feat: Add Academy, Whistleblower, Incidents, Vendor, DSB, SSO, Reporting, Multi-Tenant and Industry backends

Go handlers, models, stores and migrations for all SDK modules.
Updates developer portal navigation and BYOEH page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Boenisch
2026-02-13 21:11:27 +01:00
parent 364d2c69ff
commit 504dd3591b
40 changed files with 13105 additions and 7 deletions

View File

@@ -6,8 +6,8 @@
-- Roadmaps table
CREATE TABLE IF NOT EXISTS roadmaps (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES namespaces(id) ON DELETE SET NULL,
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
@@ -93,7 +93,7 @@ CREATE TABLE IF NOT EXISTS roadmap_items (
-- Import jobs table
CREATE TABLE IF NOT EXISTS roadmap_import_jobs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
roadmap_id UUID REFERENCES roadmaps(id) ON DELETE SET NULL,
-- File info

View File

@@ -6,8 +6,8 @@
-- Workshop sessions table
CREATE TABLE IF NOT EXISTS workshop_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES namespaces(id) ON DELETE SET NULL,
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL,
-- Session info
title VARCHAR(255) NOT NULL,

View File

@@ -6,8 +6,8 @@
-- Portfolios table
CREATE TABLE IF NOT EXISTS portfolios (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES namespaces(id) ON DELETE SET NULL,
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL,
-- Info
name VARCHAR(255) NOT NULL,

View File

@@ -0,0 +1,159 @@
-- ============================================================================
-- Migration 008: Academy (E-Learning / Compliance Academy) Schema
-- Compliance training courses, enrollments, and certificate management
-- ============================================================================
-- Academy courses table
CREATE TABLE IF NOT EXISTS academy_courses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
-- Course info
title VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(50) NOT NULL, -- 'dsgvo_basics', 'it_security', 'ai_literacy', 'whistleblower_protection', 'custom'
duration_minutes INT DEFAULT 0,
required_for_roles JSONB DEFAULT '[]', -- Array of role strings
-- Status
is_active BOOLEAN DEFAULT TRUE,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Academy lessons table
CREATE TABLE IF NOT EXISTS academy_lessons (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
course_id UUID NOT NULL REFERENCES academy_courses(id) ON DELETE CASCADE,
-- Lesson info
title VARCHAR(255) NOT NULL,
description TEXT,
lesson_type VARCHAR(50) NOT NULL, -- 'video', 'text', 'quiz', 'interactive'
content_url TEXT,
duration_minutes INT DEFAULT 0,
order_index INT DEFAULT 0,
-- Quiz questions (only for lesson_type = 'quiz')
quiz_questions JSONB DEFAULT '[]', -- Array of {question, options, correct_index, explanation}
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Academy enrollments table
CREATE TABLE IF NOT EXISTS academy_enrollments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
course_id UUID NOT NULL REFERENCES academy_courses(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
-- User info (denormalized for reporting)
user_name VARCHAR(255) NOT NULL,
user_email VARCHAR(255) NOT NULL,
-- Progress tracking
status VARCHAR(50) DEFAULT 'not_started', -- 'not_started', 'in_progress', 'completed', 'expired'
progress_percent INT DEFAULT 0, -- 0-100
current_lesson_index INT DEFAULT 0,
-- Timestamps
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
deadline TIMESTAMPTZ,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Academy certificates table
CREATE TABLE IF NOT EXISTS academy_certificates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
enrollment_id UUID NOT NULL UNIQUE REFERENCES academy_enrollments(id) ON DELETE CASCADE,
-- Certificate info
user_name VARCHAR(255) NOT NULL,
course_title VARCHAR(255) NOT NULL,
issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
valid_until TIMESTAMPTZ,
pdf_url TEXT,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- Indexes
-- ============================================================================
-- Course indexes
CREATE INDEX IF NOT EXISTS idx_academy_courses_tenant ON academy_courses(tenant_id);
CREATE INDEX IF NOT EXISTS idx_academy_courses_category ON academy_courses(tenant_id, category);
CREATE INDEX IF NOT EXISTS idx_academy_courses_active ON academy_courses(tenant_id, is_active);
-- Lesson indexes
CREATE INDEX IF NOT EXISTS idx_academy_lessons_course ON academy_lessons(course_id);
CREATE INDEX IF NOT EXISTS idx_academy_lessons_order ON academy_lessons(course_id, order_index);
-- Enrollment indexes
CREATE INDEX IF NOT EXISTS idx_academy_enrollments_tenant ON academy_enrollments(tenant_id);
CREATE INDEX IF NOT EXISTS idx_academy_enrollments_course ON academy_enrollments(course_id);
CREATE INDEX IF NOT EXISTS idx_academy_enrollments_user ON academy_enrollments(user_id);
CREATE INDEX IF NOT EXISTS idx_academy_enrollments_status ON academy_enrollments(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_academy_enrollments_deadline ON academy_enrollments(deadline) WHERE deadline IS NOT NULL AND status NOT IN ('completed', 'expired');
CREATE INDEX IF NOT EXISTS idx_academy_enrollments_tenant_course ON academy_enrollments(tenant_id, course_id);
-- Certificate indexes
CREATE INDEX IF NOT EXISTS idx_academy_certificates_enrollment ON academy_certificates(enrollment_id);
CREATE INDEX IF NOT EXISTS idx_academy_certificates_valid_until ON academy_certificates(valid_until) WHERE valid_until IS NOT NULL;
-- ============================================================================
-- Triggers
-- ============================================================================
-- Reuse existing update_updated_at_column function
-- Courses trigger
DROP TRIGGER IF EXISTS update_academy_courses_updated_at ON academy_courses;
CREATE TRIGGER update_academy_courses_updated_at
BEFORE UPDATE ON academy_courses
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Lessons trigger
DROP TRIGGER IF EXISTS update_academy_lessons_updated_at ON academy_lessons;
CREATE TRIGGER update_academy_lessons_updated_at
BEFORE UPDATE ON academy_lessons
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Enrollments trigger
DROP TRIGGER IF EXISTS update_academy_enrollments_updated_at ON academy_enrollments;
CREATE TRIGGER update_academy_enrollments_updated_at
BEFORE UPDATE ON academy_enrollments
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Certificates trigger
DROP TRIGGER IF EXISTS update_academy_certificates_updated_at ON academy_certificates;
CREATE TRIGGER update_academy_certificates_updated_at
BEFORE UPDATE ON academy_certificates
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- Comments
-- ============================================================================
COMMENT ON TABLE academy_courses IS 'Compliance training courses (DSGVO, IT-Security, AI Literacy, Whistleblower)';
COMMENT ON TABLE academy_lessons IS 'Individual lessons within a course (video, text, quiz, interactive)';
COMMENT ON TABLE academy_enrollments IS 'User enrollments in courses with progress tracking';
COMMENT ON TABLE academy_certificates IS 'Completion certificates issued for finished enrollments';
COMMENT ON COLUMN academy_courses.category IS 'Course category: dsgvo_basics, it_security, ai_literacy, whistleblower_protection, custom';
COMMENT ON COLUMN academy_courses.required_for_roles IS 'JSON array of role names that are required to complete this course';
COMMENT ON COLUMN academy_lessons.quiz_questions IS 'JSON array of quiz questions: [{question, options, correct_index, explanation}]';
COMMENT ON COLUMN academy_enrollments.status IS 'Enrollment status: not_started, in_progress, completed, expired';
COMMENT ON COLUMN academy_enrollments.progress_percent IS 'Course completion percentage (0-100)';
COMMENT ON COLUMN academy_certificates.enrollment_id IS 'One-to-one relationship with enrollment (UNIQUE constraint)';

View File

@@ -0,0 +1,141 @@
-- ============================================================================
-- Migration 009: Whistleblower / Hinweisgebersystem (HinSchG)
-- Implements the German Whistleblower Protection Act (Hinweisgeberschutzgesetz)
-- ============================================================================
-- Whistleblower reports table
CREATE TABLE IF NOT EXISTS whistleblower_reports (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
-- Identification
reference_number VARCHAR(20) NOT NULL UNIQUE, -- e.g. "WB-2026-0001"
access_key VARCHAR(50) NOT NULL UNIQUE, -- for anonymous reporter access
-- Report content
category VARCHAR(50) NOT NULL, -- corruption, fraud, data_protection, discrimination, environment, competition, product_safety, tax_evasion, other
status VARCHAR(50) NOT NULL DEFAULT 'new', -- new, acknowledged, under_review, investigation, measures_taken, closed, rejected
title VARCHAR(500) NOT NULL,
description TEXT NOT NULL,
-- Reporter info (nullable for anonymous reports)
is_anonymous BOOLEAN NOT NULL DEFAULT TRUE,
reporter_name VARCHAR(255),
reporter_email VARCHAR(255),
reporter_phone VARCHAR(100),
-- HinSchG deadlines
received_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deadline_acknowledgment TIMESTAMPTZ NOT NULL, -- 7 days from received_at per HinSchG
deadline_feedback TIMESTAMPTZ NOT NULL, -- 3 months from received_at per HinSchG
-- Status timestamps
acknowledged_at TIMESTAMPTZ,
closed_at TIMESTAMPTZ,
-- Assignment
assigned_to UUID, -- user responsible for handling
-- Audit trail (JSONB array of {timestamp, action, user_id, details})
audit_trail JSONB NOT NULL DEFAULT '[]',
-- Resolution
resolution TEXT,
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Whistleblower messages table (anonymous communication channel)
CREATE TABLE IF NOT EXISTS whistleblower_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
report_id UUID NOT NULL REFERENCES whistleblower_reports(id) ON DELETE CASCADE,
-- Message
direction VARCHAR(30) NOT NULL, -- reporter_to_admin, admin_to_reporter
content TEXT NOT NULL,
sent_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
read_at TIMESTAMPTZ
);
-- Whistleblower measures table (corrective measures)
CREATE TABLE IF NOT EXISTS whistleblower_measures (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
report_id UUID NOT NULL REFERENCES whistleblower_reports(id) ON DELETE CASCADE,
-- Measure details
title VARCHAR(500) NOT NULL,
description TEXT,
status VARCHAR(50) NOT NULL DEFAULT 'planned', -- planned, in_progress, completed
responsible VARCHAR(255),
due_date TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Sequence table for reference number generation
CREATE TABLE IF NOT EXISTS whistleblower_sequences (
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
year INT NOT NULL,
last_sequence INT NOT NULL DEFAULT 0,
PRIMARY KEY (tenant_id, year)
);
-- ============================================================================
-- Indexes
-- ============================================================================
-- Report indexes
CREATE INDEX IF NOT EXISTS idx_whistleblower_reports_tenant ON whistleblower_reports(tenant_id);
CREATE INDEX IF NOT EXISTS idx_whistleblower_reports_access_key ON whistleblower_reports(access_key);
CREATE INDEX IF NOT EXISTS idx_whistleblower_reports_reference ON whistleblower_reports(reference_number);
CREATE INDEX IF NOT EXISTS idx_whistleblower_reports_status ON whistleblower_reports(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_whistleblower_reports_category ON whistleblower_reports(tenant_id, category);
CREATE INDEX IF NOT EXISTS idx_whistleblower_reports_received ON whistleblower_reports(tenant_id, received_at);
CREATE INDEX IF NOT EXISTS idx_whistleblower_reports_deadlines ON whistleblower_reports(deadline_acknowledgment, deadline_feedback)
WHERE acknowledged_at IS NULL OR closed_at IS NULL;
-- Message indexes
CREATE INDEX IF NOT EXISTS idx_whistleblower_messages_report ON whistleblower_messages(report_id);
CREATE INDEX IF NOT EXISTS idx_whistleblower_messages_sent ON whistleblower_messages(report_id, sent_at);
-- Measure indexes
CREATE INDEX IF NOT EXISTS idx_whistleblower_measures_report ON whistleblower_measures(report_id);
CREATE INDEX IF NOT EXISTS idx_whistleblower_measures_status ON whistleblower_measures(report_id, status);
-- ============================================================================
-- Triggers
-- ============================================================================
-- Reuse existing update_updated_at_column function
-- Reports trigger
DROP TRIGGER IF EXISTS update_whistleblower_reports_updated_at ON whistleblower_reports;
CREATE TRIGGER update_whistleblower_reports_updated_at
BEFORE UPDATE ON whistleblower_reports
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Measures trigger
DROP TRIGGER IF EXISTS update_whistleblower_measures_updated_at ON whistleblower_measures;
CREATE TRIGGER update_whistleblower_measures_updated_at
BEFORE UPDATE ON whistleblower_measures
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- Comments
-- ============================================================================
COMMENT ON TABLE whistleblower_reports IS 'Whistleblower reports per HinSchG (Hinweisgeberschutzgesetz)';
COMMENT ON TABLE whistleblower_messages IS 'Anonymous communication channel between reporter and admin';
COMMENT ON TABLE whistleblower_measures IS 'Corrective measures taken for whistleblower reports';
COMMENT ON TABLE whistleblower_sequences IS 'Sequence numbers for reference number generation per tenant and year';
COMMENT ON COLUMN whistleblower_reports.reference_number IS 'Human-readable reference number (e.g. WB-2026-0001)';
COMMENT ON COLUMN whistleblower_reports.access_key IS 'Secret key for anonymous reporter to access their report';
COMMENT ON COLUMN whistleblower_reports.deadline_acknowledgment IS '7-day deadline per HinSchG §17 Abs. 1';
COMMENT ON COLUMN whistleblower_reports.deadline_feedback IS '3-month deadline per HinSchG §17 Abs. 2';
COMMENT ON COLUMN whistleblower_reports.audit_trail IS 'JSON array of audit entries tracking all actions on the report';

View File

@@ -0,0 +1,111 @@
-- ============================================================================
-- Migration 010: Incident/Breach Management Schema
-- DSGVO Art. 33 (Authority Notification) & Art. 34 (Data Subject Notification)
--
-- Art. 33 requires notification of the supervisory authority within 72 hours
-- of becoming aware of a personal data breach, unless the breach is unlikely
-- to result in a risk to the rights and freedoms of natural persons.
--
-- Art. 34 requires notification of affected data subjects without undue delay
-- when the breach is likely to result in a high risk to their rights and freedoms.
-- ============================================================================
-- Incident incidents table
CREATE TABLE IF NOT EXISTS incident_incidents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
-- Incident info
title VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(50) NOT NULL, -- data_breach, unauthorized_access, data_loss, system_compromise, phishing, ransomware, insider_threat, physical_breach, other
status VARCHAR(50) DEFAULT 'detected', -- detected, assessment, containment, notification_required, notification_sent, remediation, closed
severity VARCHAR(50) NOT NULL, -- critical, high, medium, low
-- Detection & reporting
detected_at TIMESTAMPTZ NOT NULL,
reported_by UUID NOT NULL,
-- Affected scope
affected_data_categories JSONB DEFAULT '[]', -- e.g. ["personal_data", "health_data", "financial_data"]
affected_data_subject_count INT DEFAULT 0,
affected_systems JSONB DEFAULT '[]', -- e.g. ["crm", "email_server", "database"]
-- Assessments & notifications (JSONB embedded objects)
risk_assessment JSONB, -- {likelihood, impact, risk_level, assessed_at, assessed_by, notes}
authority_notification JSONB, -- {status, deadline, submitted_at, authority_name, reference_number, contact_person, notes}
data_subject_notification JSONB, -- {required, status, sent_at, affected_count, notification_text, channel}
-- Resolution
root_cause TEXT,
lessons_learned TEXT,
-- Timeline (JSONB array of events)
timeline JSONB DEFAULT '[]', -- [{timestamp, action, user_id, details}, ...]
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
closed_at TIMESTAMPTZ
);
-- Incident measures table (corrective and preventive measures)
CREATE TABLE IF NOT EXISTS incident_measures (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
incident_id UUID NOT NULL REFERENCES incident_incidents(id) ON DELETE CASCADE,
-- Measure info
title VARCHAR(255) NOT NULL,
description TEXT,
measure_type VARCHAR(50) NOT NULL, -- immediate, long_term
status VARCHAR(50) DEFAULT 'planned', -- planned, in_progress, completed
responsible VARCHAR(255),
due_date TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- Indexes
-- ============================================================================
-- Incident indexes
CREATE INDEX IF NOT EXISTS idx_incident_incidents_tenant ON incident_incidents(tenant_id);
CREATE INDEX IF NOT EXISTS idx_incident_incidents_status ON incident_incidents(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_incident_incidents_severity ON incident_incidents(tenant_id, severity);
CREATE INDEX IF NOT EXISTS idx_incident_incidents_detected_at ON incident_incidents(detected_at DESC);
CREATE INDEX IF NOT EXISTS idx_incident_incidents_category ON incident_incidents(tenant_id, category);
-- Measure indexes
CREATE INDEX IF NOT EXISTS idx_incident_measures_incident ON incident_measures(incident_id);
CREATE INDEX IF NOT EXISTS idx_incident_measures_status ON incident_measures(incident_id, status);
-- ============================================================================
-- Triggers
-- ============================================================================
-- Reuse existing update_updated_at_column function
-- Incidents trigger
DROP TRIGGER IF EXISTS update_incident_incidents_updated_at ON incident_incidents;
CREATE TRIGGER update_incident_incidents_updated_at
BEFORE UPDATE ON incident_incidents
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- Comments
-- ============================================================================
COMMENT ON TABLE incident_incidents IS 'Security and data breach incidents per DSGVO Art. 33/34';
COMMENT ON TABLE incident_measures IS 'Corrective and preventive measures for incidents';
COMMENT ON COLUMN incident_incidents.detected_at IS 'When the incident was first detected - starts the 72h Art. 33 notification clock';
COMMENT ON COLUMN incident_incidents.authority_notification IS 'JSONB: Supervisory authority notification tracking per DSGVO Art. 33 (72h deadline)';
COMMENT ON COLUMN incident_incidents.data_subject_notification IS 'JSONB: Data subject notification tracking per DSGVO Art. 34';
COMMENT ON COLUMN incident_incidents.risk_assessment IS 'JSONB: Risk assessment with likelihood, impact, and auto-calculated risk level';
COMMENT ON COLUMN incident_incidents.timeline IS 'JSONB array: Chronological record of all actions taken during incident response';
COMMENT ON COLUMN incident_incidents.affected_data_categories IS 'JSONB array: Categories of personal data affected (e.g. health, financial)';
COMMENT ON COLUMN incident_incidents.affected_systems IS 'JSONB array: Systems affected by the incident';
COMMENT ON COLUMN incident_measures.measure_type IS 'immediate = short-term containment, long_term = preventive/structural fix';

View File

@@ -0,0 +1,356 @@
-- ============================================================================
-- Migration 011: Vendor Compliance Schema
-- Vendor Management, Contract/AVV Management, Findings, Templates
--
-- Implements DSGVO Art. 28 (Auftragsverarbeitung) requirements:
-- - Vendor registry with risk scoring and classification
-- - Contract/AVV document management with AI-assisted review
-- - Compliance findings from contract analysis
-- - Control instances for vendor-level control assessments
-- - Pre-filled templates for vendors, processing activities, and TOMs
-- ============================================================================
-- ============================================================================
-- Vendors (Service Provider Registry)
-- ============================================================================
CREATE TABLE IF NOT EXISTS vendor_vendors (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
-- Basic info
name VARCHAR(255) NOT NULL,
legal_form VARCHAR(100),
country VARCHAR(10) NOT NULL DEFAULT 'DE', -- ISO 3166-1 alpha-2
address JSONB, -- {street, city, postalCode, country, state}
website VARCHAR(500),
-- Contact
contact_name VARCHAR(255),
contact_email VARCHAR(255),
contact_phone VARCHAR(100),
contact_department VARCHAR(255),
-- Role & Classification
role VARCHAR(50) NOT NULL DEFAULT 'PROCESSOR', -- PROCESSOR, CONTROLLER, JOINT_CONTROLLER, SUB_PROCESSOR, THIRD_PARTY
service_category VARCHAR(50), -- HOSTING, CRM, ERP, ANALYTICS, etc. (19 categories)
service_description TEXT,
data_access_level VARCHAR(50) DEFAULT 'NONE', -- NONE, POTENTIAL, ADMINISTRATIVE, CONTENT
-- Processing & Compliance
processing_locations JSONB DEFAULT '[]', -- [{country, region, isPrimary, isEU, isAdequate}]
certifications JSONB DEFAULT '[]', -- ["ISO 27001", "SOC 2", etc.]
-- Risk Scoring (0-100)
inherent_risk_score INT DEFAULT 0,
residual_risk_score INT DEFAULT 0,
manual_risk_adjustment INT,
-- Contract & Review
review_frequency VARCHAR(50) DEFAULT 'ANNUAL', -- QUARTERLY, SEMI_ANNUAL, ANNUAL, BIENNIAL
last_review_date TIMESTAMPTZ,
next_review_date TIMESTAMPTZ,
-- Links
processing_activity_ids JSONB DEFAULT '[]', -- UUIDs of linked processing activities
-- Status
status VARCHAR(50) DEFAULT 'ACTIVE', -- ACTIVE, INACTIVE, PENDING_REVIEW, TERMINATED
-- Template reference (if created from template)
template_id VARCHAR(100),
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID NOT NULL
);
-- ============================================================================
-- Contracts (including AVV/DPA)
-- ============================================================================
CREATE TABLE IF NOT EXISTS vendor_contracts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
vendor_id UUID NOT NULL REFERENCES vendor_vendors(id) ON DELETE CASCADE,
-- Document info
file_name VARCHAR(500),
original_name VARCHAR(500),
mime_type VARCHAR(100),
file_size BIGINT,
storage_path VARCHAR(1000), -- MinIO path
-- Classification
document_type VARCHAR(50) NOT NULL, -- AVV, MSA, SLA, SCC, NDA, TOM_ANNEX, CERTIFICATION, SUB_PROCESSOR_LIST
-- Metadata (extracted or manual)
parties JSONB, -- [{name, role, address}]
effective_date DATE,
expiration_date DATE,
auto_renewal BOOLEAN DEFAULT FALSE,
renewal_notice_period VARCHAR(100),
-- Review Status
review_status VARCHAR(50) DEFAULT 'PENDING', -- PENDING, IN_PROGRESS, COMPLETED, FAILED
review_completed_at TIMESTAMPTZ,
compliance_score INT, -- 0-100
-- Versioning
version VARCHAR(50) DEFAULT '1.0',
previous_version_id UUID REFERENCES vendor_contracts(id),
-- Content (extracted text for analysis)
extracted_text TEXT,
page_count INT,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID NOT NULL
);
-- ============================================================================
-- Findings (from contract reviews)
-- ============================================================================
CREATE TABLE IF NOT EXISTS vendor_findings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
contract_id UUID REFERENCES vendor_contracts(id) ON DELETE CASCADE,
vendor_id UUID NOT NULL REFERENCES vendor_vendors(id) ON DELETE CASCADE,
-- Classification
finding_type VARCHAR(20) NOT NULL, -- OK, GAP, RISK, UNKNOWN
category VARCHAR(50) NOT NULL, -- AVV_CONTENT, SUBPROCESSOR, INCIDENT, AUDIT_RIGHTS, DELETION, TOM, TRANSFER, LIABILITY, SLA, DATA_SUBJECT_RIGHTS, CONFIDENTIALITY, INSTRUCTION, TERMINATION, GENERAL
severity VARCHAR(20) NOT NULL, -- LOW, MEDIUM, HIGH, CRITICAL
-- Content
title VARCHAR(500) NOT NULL,
description TEXT,
recommendation TEXT,
-- Citations (from contract text)
citations JSONB DEFAULT '[]', -- [{documentId, page, startChar, endChar, quotedText, quoteHash}]
-- Workflow
status VARCHAR(50) DEFAULT 'OPEN', -- OPEN, IN_PROGRESS, RESOLVED, ACCEPTED, FALSE_POSITIVE
assignee VARCHAR(255),
due_date DATE,
resolution TEXT,
resolved_at TIMESTAMPTZ,
resolved_by UUID,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- Control Instances (applied controls per vendor)
-- ============================================================================
CREATE TABLE IF NOT EXISTS vendor_control_instances (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
vendor_id UUID NOT NULL REFERENCES vendor_vendors(id) ON DELETE CASCADE,
-- Control reference
control_id VARCHAR(100) NOT NULL, -- e.g., VND-TRF-01
control_domain VARCHAR(50), -- TRANSFER, AUDIT, DELETION, INCIDENT, SUBPROCESSOR, TOM, CONTRACT, DATA_SUBJECT, SECURITY, GOVERNANCE
-- Assessment
status VARCHAR(50) DEFAULT 'PLANNED', -- PASS, PARTIAL, FAIL, NOT_APPLICABLE, PLANNED
evidence_ids JSONB DEFAULT '[]',
notes TEXT,
-- Timing
last_assessed_at TIMESTAMPTZ,
last_assessed_by UUID,
next_assessment_date TIMESTAMPTZ,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(tenant_id, vendor_id, control_id)
);
-- ============================================================================
-- Templates (pre-filled templates for various entity types)
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES compliance_tenants(id) ON DELETE CASCADE, -- NULL for system templates
-- Template info
template_type VARCHAR(50) NOT NULL, -- VENDOR, PROCESSING_ACTIVITY, TOM, CONTROL_SET
template_id VARCHAR(100) NOT NULL UNIQUE, -- e.g., tpl-vendor-cloud-iaas
category VARCHAR(100), -- HR, SALES, MARKETING, CLOUD_INFRASTRUCTURE, etc.
-- Content
name_de VARCHAR(500) NOT NULL,
name_en VARCHAR(500) NOT NULL,
description_de TEXT,
description_en TEXT,
-- Template data (full template content as JSONB)
template_data JSONB NOT NULL,
-- Organization
industry VARCHAR(100), -- IT, HEALTHCARE, FINANCE, MANUFACTURING, RETAIL, etc.
tags JSONB DEFAULT '[]',
-- Metadata
is_system BOOLEAN DEFAULT FALSE, -- true = pre-installed, false = user-created
is_active BOOLEAN DEFAULT TRUE,
usage_count INT DEFAULT 0,
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- Indexes: Vendors
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_vendor_vendors_tenant ON vendor_vendors(tenant_id);
CREATE INDEX IF NOT EXISTS idx_vendor_vendors_status ON vendor_vendors(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_vendor_vendors_role ON vendor_vendors(tenant_id, role);
CREATE INDEX IF NOT EXISTS idx_vendor_vendors_service_category ON vendor_vendors(tenant_id, service_category);
CREATE INDEX IF NOT EXISTS idx_vendor_vendors_next_review ON vendor_vendors(next_review_date)
WHERE next_review_date IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_vendor_vendors_template_id ON vendor_vendors(template_id)
WHERE template_id IS NOT NULL;
-- ============================================================================
-- Indexes: Contracts
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_vendor_contracts_tenant ON vendor_contracts(tenant_id);
CREATE INDEX IF NOT EXISTS idx_vendor_contracts_vendor ON vendor_contracts(vendor_id);
CREATE INDEX IF NOT EXISTS idx_vendor_contracts_document_type ON vendor_contracts(tenant_id, document_type);
CREATE INDEX IF NOT EXISTS idx_vendor_contracts_review_status ON vendor_contracts(tenant_id, review_status);
CREATE INDEX IF NOT EXISTS idx_vendor_contracts_expiration ON vendor_contracts(expiration_date)
WHERE expiration_date IS NOT NULL;
-- ============================================================================
-- Indexes: Findings
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_vendor_findings_tenant ON vendor_findings(tenant_id);
CREATE INDEX IF NOT EXISTS idx_vendor_findings_vendor ON vendor_findings(vendor_id);
CREATE INDEX IF NOT EXISTS idx_vendor_findings_contract ON vendor_findings(contract_id)
WHERE contract_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_vendor_findings_severity ON vendor_findings(tenant_id, severity);
CREATE INDEX IF NOT EXISTS idx_vendor_findings_status ON vendor_findings(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_vendor_findings_category ON vendor_findings(tenant_id, category);
-- ============================================================================
-- Indexes: Control Instances
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_vendor_control_instances_tenant ON vendor_control_instances(tenant_id);
CREATE INDEX IF NOT EXISTS idx_vendor_control_instances_vendor ON vendor_control_instances(vendor_id);
CREATE INDEX IF NOT EXISTS idx_vendor_control_instances_control_id ON vendor_control_instances(control_id);
CREATE INDEX IF NOT EXISTS idx_vendor_control_instances_status ON vendor_control_instances(tenant_id, status);
-- ============================================================================
-- Indexes: Templates
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_compliance_templates_type ON compliance_templates(template_type);
CREATE INDEX IF NOT EXISTS idx_compliance_templates_category ON compliance_templates(category)
WHERE category IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_compliance_templates_industry ON compliance_templates(industry)
WHERE industry IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_compliance_templates_system ON compliance_templates(is_system);
CREATE INDEX IF NOT EXISTS idx_compliance_templates_active ON compliance_templates(is_active);
CREATE INDEX IF NOT EXISTS idx_compliance_templates_template_id ON compliance_templates(template_id);
-- ============================================================================
-- Triggers
-- ============================================================================
-- Reuse existing update_updated_at_column function
-- Vendors trigger
DROP TRIGGER IF EXISTS update_vendor_vendors_updated_at ON vendor_vendors;
CREATE TRIGGER update_vendor_vendors_updated_at
BEFORE UPDATE ON vendor_vendors
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Contracts trigger
DROP TRIGGER IF EXISTS update_vendor_contracts_updated_at ON vendor_contracts;
CREATE TRIGGER update_vendor_contracts_updated_at
BEFORE UPDATE ON vendor_contracts
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Findings trigger
DROP TRIGGER IF EXISTS update_vendor_findings_updated_at ON vendor_findings;
CREATE TRIGGER update_vendor_findings_updated_at
BEFORE UPDATE ON vendor_findings
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Control instances trigger
DROP TRIGGER IF EXISTS update_vendor_control_instances_updated_at ON vendor_control_instances;
CREATE TRIGGER update_vendor_control_instances_updated_at
BEFORE UPDATE ON vendor_control_instances
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Templates trigger
DROP TRIGGER IF EXISTS update_compliance_templates_updated_at ON compliance_templates;
CREATE TRIGGER update_compliance_templates_updated_at
BEFORE UPDATE ON compliance_templates
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- Comments
-- ============================================================================
-- Table comments
COMMENT ON TABLE vendor_vendors IS 'Service provider registry for vendor compliance management (DSGVO Art. 28)';
COMMENT ON TABLE vendor_contracts IS 'Contract and AVV/DPA document management with AI-assisted review';
COMMENT ON TABLE vendor_findings IS 'Compliance findings from contract reviews and vendor assessments';
COMMENT ON TABLE vendor_control_instances IS 'Applied controls per vendor with assessment tracking';
COMMENT ON TABLE compliance_templates IS 'Pre-filled templates for vendors, processing activities, TOMs, and control sets';
-- Vendor column comments
COMMENT ON COLUMN vendor_vendors.role IS 'DSGVO role: PROCESSOR (Art. 28), CONTROLLER, JOINT_CONTROLLER (Art. 26), SUB_PROCESSOR, THIRD_PARTY';
COMMENT ON COLUMN vendor_vendors.data_access_level IS 'Level of access to personal data: NONE, POTENTIAL, ADMINISTRATIVE, CONTENT';
COMMENT ON COLUMN vendor_vendors.processing_locations IS 'JSONB array: Data processing locations with EU/adequacy status for transfer assessment';
COMMENT ON COLUMN vendor_vendors.certifications IS 'JSONB array: Vendor certifications (ISO 27001, SOC 2, etc.)';
COMMENT ON COLUMN vendor_vendors.inherent_risk_score IS 'Risk score (0-100) before controls are applied';
COMMENT ON COLUMN vendor_vendors.residual_risk_score IS 'Risk score (0-100) after controls are applied';
COMMENT ON COLUMN vendor_vendors.review_frequency IS 'How often the vendor must be reviewed: QUARTERLY, SEMI_ANNUAL, ANNUAL, BIENNIAL';
COMMENT ON COLUMN vendor_vendors.processing_activity_ids IS 'JSONB array: UUIDs linking to dsgvo_processing_activities entries';
COMMENT ON COLUMN vendor_vendors.template_id IS 'Reference to compliance_templates.template_id if vendor was created from a template';
-- Contract column comments
COMMENT ON COLUMN vendor_contracts.document_type IS 'Contract type: AVV (Auftragsverarbeitungsvertrag), MSA, SLA, SCC (Standard Contractual Clauses), NDA, TOM_ANNEX, CERTIFICATION, SUB_PROCESSOR_LIST';
COMMENT ON COLUMN vendor_contracts.storage_path IS 'MinIO object storage path for the uploaded document';
COMMENT ON COLUMN vendor_contracts.compliance_score IS 'AI-assessed compliance score (0-100) from contract review';
COMMENT ON COLUMN vendor_contracts.extracted_text IS 'Full text extracted from the document for AI analysis';
COMMENT ON COLUMN vendor_contracts.previous_version_id IS 'Self-referencing FK for contract version history';
-- Finding column comments
COMMENT ON COLUMN vendor_findings.finding_type IS 'Classification: OK (compliant), GAP (missing clause), RISK (problematic clause), UNKNOWN (could not determine)';
COMMENT ON COLUMN vendor_findings.category IS 'DSGVO Art. 28 requirement category the finding relates to';
COMMENT ON COLUMN vendor_findings.citations IS 'JSONB array: References to specific contract text passages with page/character offsets';
COMMENT ON COLUMN vendor_findings.status IS 'Workflow status: OPEN, IN_PROGRESS, RESOLVED, ACCEPTED (risk accepted), FALSE_POSITIVE';
-- Control instance column comments
COMMENT ON COLUMN vendor_control_instances.control_id IS 'Control identifier (e.g., VND-TRF-01 for transfer controls)';
COMMENT ON COLUMN vendor_control_instances.control_domain IS 'Control domain: TRANSFER, AUDIT, DELETION, INCIDENT, SUBPROCESSOR, TOM, CONTRACT, DATA_SUBJECT, SECURITY, GOVERNANCE';
COMMENT ON COLUMN vendor_control_instances.status IS 'Assessment result: PASS, PARTIAL, FAIL, NOT_APPLICABLE, PLANNED';
COMMENT ON COLUMN vendor_control_instances.evidence_ids IS 'JSONB array: References to evidence documents or contract IDs';
-- Template column comments
COMMENT ON COLUMN compliance_templates.template_type IS 'Template category: VENDOR, PROCESSING_ACTIVITY, TOM, CONTROL_SET';
COMMENT ON COLUMN compliance_templates.template_id IS 'Human-readable unique identifier (e.g., tpl-vendor-cloud-iaas)';
COMMENT ON COLUMN compliance_templates.template_data IS 'JSONB: Full template content including all pre-filled fields';
COMMENT ON COLUMN compliance_templates.is_system IS 'true = pre-installed system template, false = user-created tenant template';
COMMENT ON COLUMN compliance_templates.usage_count IS 'Number of times this template has been used to create entities';

View File

@@ -0,0 +1,175 @@
-- ============================================================================
-- Migration 013: DSB-as-a-Service Portal Schema
-- Datenschutzbeauftragter (Data Protection Officer) Portal
--
-- Provides a portal for external DSBs to manage multiple client tenants,
-- track hours, manage tasks, and communicate.
--
-- Depends on: 001_rbac_schema.sql (compliance_tenants)
-- ============================================================================
-- ============================================================================
-- DSB Assignments: which DSB is assigned to which tenant
-- ============================================================================
CREATE TABLE IF NOT EXISTS dsb_assignments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dsb_user_id UUID NOT NULL, -- the DSB user
tenant_id UUID NOT NULL REFERENCES compliance_tenants(id),
status VARCHAR(20) NOT NULL DEFAULT 'active', -- active, paused, terminated
contract_start DATE NOT NULL,
contract_end DATE,
monthly_hours_budget DECIMAL(5,1) DEFAULT 0,
notes TEXT DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(dsb_user_id, tenant_id)
);
-- ============================================================================
-- DSB Time Tracking
-- ============================================================================
CREATE TABLE IF NOT EXISTS dsb_hours (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
assignment_id UUID NOT NULL REFERENCES dsb_assignments(id) ON DELETE CASCADE,
date DATE NOT NULL,
hours DECIMAL(4,1) NOT NULL,
category VARCHAR(50) NOT NULL, -- 'dsfa_review', 'consultation', 'audit', 'training', 'incident_response', 'documentation', 'meeting', 'other'
description TEXT NOT NULL,
billable BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- DSB Tasks / Queue
-- ============================================================================
CREATE TABLE IF NOT EXISTS dsb_tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
assignment_id UUID NOT NULL REFERENCES dsb_assignments(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
description TEXT DEFAULT '',
category VARCHAR(50) NOT NULL, -- 'dsfa_review', 'dsr_response', 'incident_review', 'audit_preparation', 'policy_review', 'training', 'consultation', 'other'
priority VARCHAR(20) NOT NULL DEFAULT 'medium', -- low, medium, high, urgent
status VARCHAR(20) NOT NULL DEFAULT 'open', -- open, in_progress, waiting, completed, cancelled
due_date DATE,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- DSB Communication Log
-- ============================================================================
CREATE TABLE IF NOT EXISTS dsb_communications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
assignment_id UUID NOT NULL REFERENCES dsb_assignments(id) ON DELETE CASCADE,
direction VARCHAR(10) NOT NULL, -- 'inbound', 'outbound'
channel VARCHAR(20) NOT NULL, -- 'email', 'phone', 'meeting', 'portal', 'letter'
subject VARCHAR(255) NOT NULL,
content TEXT DEFAULT '',
participants TEXT DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- Indexes: DSB Assignments
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_dsb_assignments_dsb_user_id ON dsb_assignments(dsb_user_id);
CREATE INDEX IF NOT EXISTS idx_dsb_assignments_tenant_id ON dsb_assignments(tenant_id);
CREATE INDEX IF NOT EXISTS idx_dsb_assignments_status ON dsb_assignments(status);
-- ============================================================================
-- Indexes: DSB Hours
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_dsb_hours_assignment_id ON dsb_hours(assignment_id);
CREATE INDEX IF NOT EXISTS idx_dsb_hours_date ON dsb_hours(date);
-- ============================================================================
-- Indexes: DSB Tasks
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_dsb_tasks_assignment_id ON dsb_tasks(assignment_id);
CREATE INDEX IF NOT EXISTS idx_dsb_tasks_status ON dsb_tasks(status);
CREATE INDEX IF NOT EXISTS idx_dsb_tasks_priority ON dsb_tasks(priority);
CREATE INDEX IF NOT EXISTS idx_dsb_tasks_due_date ON dsb_tasks(due_date);
-- ============================================================================
-- Indexes: DSB Communications
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_dsb_communications_assignment_id ON dsb_communications(assignment_id);
-- ============================================================================
-- Triggers
-- ============================================================================
-- Ensure update_updated_at_column() function exists (created in earlier migrations)
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- DSB Assignments trigger
DROP TRIGGER IF EXISTS update_dsb_assignments_updated_at ON dsb_assignments;
CREATE TRIGGER update_dsb_assignments_updated_at
BEFORE UPDATE ON dsb_assignments
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- DSB Tasks trigger
DROP TRIGGER IF EXISTS update_dsb_tasks_updated_at ON dsb_tasks;
CREATE TRIGGER update_dsb_tasks_updated_at
BEFORE UPDATE ON dsb_tasks
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- Comments
-- ============================================================================
-- Table comments
COMMENT ON TABLE dsb_assignments IS 'DSB-as-a-Service: Maps external Data Protection Officers (DSBs) to client tenants with contract details and hour budgets';
COMMENT ON TABLE dsb_hours IS 'DSB-as-a-Service: Time tracking entries for DSB work on assigned tenants, categorized and billable';
COMMENT ON TABLE dsb_tasks IS 'DSB-as-a-Service: Task queue for DSB work items per tenant assignment with priority and status tracking';
COMMENT ON TABLE dsb_communications IS 'DSB-as-a-Service: Communication log between DSB and client tenant, tracking direction, channel, and content';
-- DSB Assignments column comments
COMMENT ON COLUMN dsb_assignments.dsb_user_id IS 'UUID of the Data Protection Officer user account';
COMMENT ON COLUMN dsb_assignments.tenant_id IS 'UUID of the client tenant this DSB is assigned to';
COMMENT ON COLUMN dsb_assignments.status IS 'Assignment status: active, paused, or terminated';
COMMENT ON COLUMN dsb_assignments.contract_start IS 'Start date of the DSB service contract';
COMMENT ON COLUMN dsb_assignments.contract_end IS 'End date of the DSB service contract (NULL for open-ended)';
COMMENT ON COLUMN dsb_assignments.monthly_hours_budget IS 'Monthly hour budget allocated for this tenant';
COMMENT ON COLUMN dsb_assignments.notes IS 'Internal notes about the assignment';
-- DSB Hours column comments
COMMENT ON COLUMN dsb_hours.assignment_id IS 'Reference to the DSB assignment this time entry belongs to';
COMMENT ON COLUMN dsb_hours.date IS 'Date the work was performed';
COMMENT ON COLUMN dsb_hours.hours IS 'Number of hours worked (e.g. 1.5)';
COMMENT ON COLUMN dsb_hours.category IS 'Work category: dsfa_review, consultation, audit, training, incident_response, documentation, meeting, other';
COMMENT ON COLUMN dsb_hours.description IS 'Description of work performed';
COMMENT ON COLUMN dsb_hours.billable IS 'Whether this time entry is billable to the client';
-- DSB Tasks column comments
COMMENT ON COLUMN dsb_tasks.assignment_id IS 'Reference to the DSB assignment this task belongs to';
COMMENT ON COLUMN dsb_tasks.title IS 'Short title describing the task';
COMMENT ON COLUMN dsb_tasks.description IS 'Detailed task description';
COMMENT ON COLUMN dsb_tasks.category IS 'Task category: dsfa_review, dsr_response, incident_review, audit_preparation, policy_review, training, consultation, other';
COMMENT ON COLUMN dsb_tasks.priority IS 'Task priority: low, medium, high, urgent';
COMMENT ON COLUMN dsb_tasks.status IS 'Task status: open, in_progress, waiting, completed, cancelled';
COMMENT ON COLUMN dsb_tasks.due_date IS 'Due date for the task (NULL if no deadline)';
COMMENT ON COLUMN dsb_tasks.completed_at IS 'Timestamp when the task was completed';
-- DSB Communications column comments
COMMENT ON COLUMN dsb_communications.assignment_id IS 'Reference to the DSB assignment this communication belongs to';
COMMENT ON COLUMN dsb_communications.direction IS 'Communication direction: inbound (from client) or outbound (from DSB)';
COMMENT ON COLUMN dsb_communications.channel IS 'Communication channel: email, phone, meeting, portal, letter';
COMMENT ON COLUMN dsb_communications.subject IS 'Subject line or topic of the communication';
COMMENT ON COLUMN dsb_communications.content IS 'Full content or summary of the communication';
COMMENT ON COLUMN dsb_communications.participants IS 'Comma-separated list of participants';