Files
breakpilot-compliance/ai-compliance-sdk/migrations/013_dsb_portal_schema.sql
Benjamin Boenisch 504dd3591b 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>
2026-02-13 21:11:27 +01:00

176 lines
9.3 KiB
PL/PgSQL

-- ============================================================================
-- 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';