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