-- ITSM-Plattform: PostgreSQL-Schema -- Mandantenfaehig (mehrere Organisationen auf einer Instanz, Daten per tenant_id getrennt). -- Enthaelt audit_log fuer Nachvollziehbarkeit gemaess ISO 27001 A.12.4 / DSGVO Art. 30. CREATE TABLE IF NOT EXISTS tenants ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), dsb_name TEXT, -- Datenschutzbeauftragter/Kontakt dsb_email TEXT, retention_tickets_days INTEGER NOT NULL DEFAULT 1095, -- DSGVO Speicherbegrenzung (Default ~3 Jahre) retention_audit_days INTEGER NOT NULL DEFAULT 1825, -- Audit-Log-Aufbewahrung (Default ~5 Jahre) sla_antwort_minuten INTEGER NOT NULL DEFAULT 480, sla_loesung_minuten INTEGER NOT NULL DEFAULT 2880 ); CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, tenant_id INTEGER NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, email TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'agent', -- 'admin' | 'agent' active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), last_login_at TIMESTAMPTZ ); CREATE TABLE IF NOT EXISTS services ( id SERIAL PRIMARY KEY, tenant_id INTEGER NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, name TEXT NOT NULL, beschreibung TEXT, kategorie TEXT, gebucht BOOLEAN NOT NULL DEFAULT TRUE, endpoint TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS tickets ( id SERIAL PRIMARY KEY, tenant_id INTEGER NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, ticket_nr TEXT NOT NULL, titel TEXT NOT NULL, beschreibung TEXT, service_id INTEGER REFERENCES services(id) ON DELETE SET NULL, status TEXT NOT NULL DEFAULT 'Offen', prioritaet TEXT NOT NULL DEFAULT 'Mittel', kategorie TEXT NOT NULL DEFAULT 'Task', zugewiesen_an TEXT, ersteller_id INTEGER REFERENCES users(id) ON DELETE SET NULL, fortschritt INTEGER NOT NULL DEFAULT 0, sla_antwort_minuten INTEGER, sla_loesung_minuten INTEGER, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (tenant_id, ticket_nr) ); CREATE TABLE IF NOT EXISTS ticket_timeline ( id SERIAL PRIMARY KEY, ticket_id INTEGER NOT NULL REFERENCES tickets(id) ON DELETE CASCADE, tenant_id INTEGER NOT NULL, zeit TIMESTAMPTZ NOT NULL DEFAULT now(), akteur TEXT, text TEXT ); CREATE TABLE IF NOT EXISTS audit_log ( id SERIAL PRIMARY KEY, tenant_id INTEGER REFERENCES tenants(id) ON DELETE CASCADE, user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, zeit TIMESTAMPTZ NOT NULL DEFAULT now(), aktion TEXT NOT NULL, entity_typ TEXT, entity_id TEXT, details JSONB, ip TEXT ); CREATE INDEX IF NOT EXISTS idx_tickets_tenant_status ON tickets (tenant_id, status); CREATE INDEX IF NOT EXISTS idx_tickets_tenant_kategorie ON tickets (tenant_id, kategorie); CREATE INDEX IF NOT EXISTS idx_timeline_ticket ON ticket_timeline (ticket_id); CREATE INDEX IF NOT EXISTS idx_audit_tenant_zeit ON audit_log (tenant_id, zeit DESC); CREATE INDEX IF NOT EXISTS idx_services_tenant ON services (tenant_id); CREATE INDEX IF NOT EXISTS idx_users_tenant ON users (tenant_id); -- Vorbereitung fuer spaetere AD/LDAP-Anbindung (Nutzer-Vorgabe 2026-07-13): -- 'local' = im ITSM selbst verwaltetes Konto (heutiger Stand), 'ldap' reserviert -- fuer die kommende Ausbaustufe. Kein Verhaltensunterschied heute, nur Vorbereitung. ALTER TABLE users ADD COLUMN IF NOT EXISTS auth_source TEXT NOT NULL DEFAULT 'local'; -- ── Wissensdatenbank ────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS kb_articles ( id SERIAL PRIMARY KEY, tenant_id INTEGER NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, titel TEXT NOT NULL, kategorie TEXT NOT NULL DEFAULT 'Allgemein', inhalt TEXT NOT NULL, tags TEXT, autor_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_kb_tenant_kategorie ON kb_articles (tenant_id, kategorie); -- ── CMDB (Configuration Management Database) ────────────────────────────────── CREATE TABLE IF NOT EXISTS configuration_items ( id SERIAL PRIMARY KEY, tenant_id INTEGER NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, name TEXT NOT NULL, ci_typ TEXT NOT NULL DEFAULT 'Sonstiges', status TEXT NOT NULL DEFAULT 'Aktiv', beschreibung TEXT, attribute JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_ci_tenant_typ ON configuration_items (tenant_id, ci_typ); CREATE INDEX IF NOT EXISTS idx_ci_tenant_status ON configuration_items (tenant_id, status); CREATE TABLE IF NOT EXISTS ci_relationships ( id SERIAL PRIMARY KEY, tenant_id INTEGER NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, from_ci_id INTEGER NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE, to_ci_id INTEGER NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE, beziehungs_typ TEXT NOT NULL DEFAULT 'haengt ab von', created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_cirel_tenant ON ci_relationships (tenant_id); CREATE INDEX IF NOT EXISTS idx_cirel_from ON ci_relationships (from_ci_id); CREATE INDEX IF NOT EXISTS idx_cirel_to ON ci_relationships (to_ci_id);