-- Migration 026: DSR (Data Subject Requests) — Betroffenenanfragen nach DSGVO Art. 15-21 -- Ersetzt Go consent-service Proxy durch native Python/FastAPI Implementierung -- Sequence für Request-Nummern CREATE SEQUENCE IF NOT EXISTS compliance_dsr_request_number_seq START WITH 1; -- Haupttabelle: DSR Requests CREATE TABLE IF NOT EXISTS compliance_dsr_requests ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL DEFAULT '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e', request_number TEXT NOT NULL, request_type TEXT NOT NULL DEFAULT 'access', status TEXT NOT NULL DEFAULT 'intake', priority TEXT NOT NULL DEFAULT 'normal', -- Antragsteller requester_name TEXT NOT NULL, requester_email TEXT NOT NULL, requester_phone TEXT, requester_address TEXT, requester_customer_id TEXT, -- Anfrage-Details source TEXT NOT NULL DEFAULT 'email', source_details TEXT, request_text TEXT, notes TEXT, internal_notes TEXT, -- Fristen (Art. 12 Abs. 3 DSGVO) received_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deadline_at TIMESTAMPTZ NOT NULL, extended_deadline_at TIMESTAMPTZ, extension_reason TEXT, extension_approved_by TEXT, extension_approved_at TIMESTAMPTZ, -- Identitaetspruefung identity_verified BOOLEAN NOT NULL DEFAULT FALSE, verification_method TEXT, verified_at TIMESTAMPTZ, verified_by TEXT, verification_notes TEXT, verification_document_ref TEXT, -- Zuweisung assigned_to TEXT, assigned_at TIMESTAMPTZ, assigned_by TEXT, -- Abschluss completed_at TIMESTAMPTZ, completion_notes TEXT, rejection_reason TEXT, rejection_legal_basis TEXT, -- Typ-spezifische Daten (JSONB) erasure_checklist JSONB DEFAULT '[]'::jsonb, data_export JSONB DEFAULT '{}'::jsonb, rectification_details JSONB DEFAULT '{}'::jsonb, objection_details JSONB DEFAULT '{}'::jsonb, affected_systems JSONB DEFAULT '[]'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by TEXT DEFAULT 'system', updated_by TEXT ); CREATE INDEX IF NOT EXISTS idx_dsr_requests_tenant ON compliance_dsr_requests(tenant_id); CREATE INDEX IF NOT EXISTS idx_dsr_requests_status ON compliance_dsr_requests(status); CREATE INDEX IF NOT EXISTS idx_dsr_requests_type ON compliance_dsr_requests(request_type); CREATE INDEX IF NOT EXISTS idx_dsr_requests_priority ON compliance_dsr_requests(priority); CREATE INDEX IF NOT EXISTS idx_dsr_requests_assigned ON compliance_dsr_requests(assigned_to); CREATE INDEX IF NOT EXISTS idx_dsr_requests_deadline ON compliance_dsr_requests(deadline_at); CREATE INDEX IF NOT EXISTS idx_dsr_requests_received ON compliance_dsr_requests(received_at); CREATE UNIQUE INDEX IF NOT EXISTS idx_dsr_requests_number ON compliance_dsr_requests(tenant_id, request_number); -- Status-History (Audit-Trail) CREATE TABLE IF NOT EXISTS compliance_dsr_status_history ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL DEFAULT '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e', dsr_id UUID NOT NULL, previous_status TEXT, new_status TEXT NOT NULL, changed_by TEXT, comment TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_dsr_history_dsr ON compliance_dsr_status_history(dsr_id); CREATE INDEX IF NOT EXISTS idx_dsr_history_created ON compliance_dsr_status_history(created_at); -- Kommunikation (E-Mail, Portal, intern) CREATE TABLE IF NOT EXISTS compliance_dsr_communications ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL DEFAULT '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e', dsr_id UUID NOT NULL, communication_type TEXT NOT NULL DEFAULT 'outgoing', channel TEXT NOT NULL DEFAULT 'email', subject TEXT, content TEXT NOT NULL, template_used TEXT, attachments JSONB DEFAULT '[]'::jsonb, sent_at TIMESTAMPTZ, sent_by TEXT, received_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by TEXT DEFAULT 'system' ); CREATE INDEX IF NOT EXISTS idx_dsr_comms_dsr ON compliance_dsr_communications(dsr_id); -- Kommunikationsvorlagen CREATE TABLE IF NOT EXISTS compliance_dsr_templates ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL DEFAULT '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e', name TEXT NOT NULL, template_type TEXT NOT NULL, request_type TEXT, language TEXT NOT NULL DEFAULT 'de', is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_dsr_templates_tenant ON compliance_dsr_templates(tenant_id); CREATE INDEX IF NOT EXISTS idx_dsr_templates_type ON compliance_dsr_templates(template_type); -- Versionierte Template-Inhalte CREATE TABLE IF NOT EXISTS compliance_dsr_template_versions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), template_id UUID NOT NULL, version TEXT NOT NULL DEFAULT '1.0', subject TEXT NOT NULL, body_html TEXT NOT NULL, body_text TEXT, status TEXT NOT NULL DEFAULT 'draft', published_at TIMESTAMPTZ, published_by TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by TEXT DEFAULT 'system' ); CREATE INDEX IF NOT EXISTS idx_dsr_tpl_versions_template ON compliance_dsr_template_versions(template_id); CREATE INDEX IF NOT EXISTS idx_dsr_tpl_versions_status ON compliance_dsr_template_versions(status); -- Art. 17(3) Ausnahmepruefungen CREATE TABLE IF NOT EXISTS compliance_dsr_exception_checks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL DEFAULT '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e', dsr_id UUID NOT NULL, check_code TEXT NOT NULL, article TEXT NOT NULL, label TEXT NOT NULL, description TEXT, applies BOOLEAN, notes TEXT, checked_by TEXT, checked_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_dsr_exception_dsr ON compliance_dsr_exception_checks(dsr_id); -- Default-Templates einfuegen INSERT INTO compliance_dsr_templates (id, name, template_type, request_type, language) VALUES (gen_random_uuid(), 'Eingangsbestaetigung', 'receipt', NULL, 'de'), (gen_random_uuid(), 'Identitaetsanfrage', 'clarification', NULL, 'de'), (gen_random_uuid(), 'Auskunft abgeschlossen', 'completion', 'access', 'de'), (gen_random_uuid(), 'Loeschung abgeschlossen', 'completion', 'erasure', 'de'), (gen_random_uuid(), 'Berichtigung abgeschlossen', 'completion', 'rectification', 'de'), (gen_random_uuid(), 'Ablehnung Auskunft', 'rejection', 'access', 'de'), (gen_random_uuid(), 'Ablehnung Loeschung', 'rejection', 'erasure', 'de'), (gen_random_uuid(), 'Fristverlaengerung', 'extension', NULL, 'de') ON CONFLICT DO NOTHING;