diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..1e20195 --- /dev/null +++ b/schema.sql @@ -0,0 +1,85 @@ +-- 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);