[split-required] Split 58 monoliths across Python, Go, TypeScript (Phases 1-3)
Phase 1 — Python (klausur-service): 5 monoliths → 36 files - dsfa_corpus_ingestion.py (1,828 LOC → 5 files) - cv_ocr_engines.py (2,102 LOC → 7 files) - cv_layout.py (3,653 LOC → 10 files) - vocab_worksheet_api.py (2,783 LOC → 8 files) - grid_build_core.py (1,958 LOC → 6 files) Phase 2 — Go (edu-search-service, school-service): 8 monoliths → 19 files - staff_crawler.go (1,402 → 4), policy/store.go (1,168 → 3) - policy_handlers.go (700 → 2), repository.go (684 → 2) - search.go (592 → 2), ai_extraction_handlers.go (554 → 2) - seed_data.go (591 → 2), grade_service.go (646 → 2) Phase 3 — TypeScript (admin-lehrer): 45 monoliths → 220+ files - sdk/types.ts (2,108 → 16 domain files) - ai/rag/page.tsx (2,686 → 14 files) - 22 page.tsx files split into _components/ + _hooks/ - 11 component files split into sub-components - 10 SDK data catalogs added to loc-exceptions - Deleted dead backup index_original.ts (4,899 LOC) All original public APIs preserved via re-export facades. Zero new errors: Python imports verified, Go builds clean, TypeScript tsc --noEmit shows only pre-existing errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
515
admin-lehrer/lib/module-registry-data.ts
Normal file
515
admin-lehrer/lib/module-registry-data.ts
Normal file
@@ -0,0 +1,515 @@
|
||||
/**
|
||||
* Module Registry Data - All backend module definitions
|
||||
*
|
||||
* This file contains the pure data array of module definitions.
|
||||
* Helper functions are in module-registry.ts.
|
||||
*/
|
||||
|
||||
import type { BackendModule } from './module-registry'
|
||||
|
||||
export const MODULE_REGISTRY: BackendModule[] = [
|
||||
// ===========================================
|
||||
// COMPLIANCE MODULES
|
||||
// ===========================================
|
||||
{
|
||||
id: 'consent-documents',
|
||||
name: 'Consent Dokumente',
|
||||
description: 'Verwaltung rechtlicher Dokumente (AGB, Datenschutz, etc.)',
|
||||
category: 'compliance',
|
||||
backend: {
|
||||
service: 'consent-service',
|
||||
port: 8081,
|
||||
basePath: '/api/consent/admin',
|
||||
endpoints: [
|
||||
{ path: '/documents', method: 'GET', description: 'Liste aller Dokumente' },
|
||||
{ path: '/documents', method: 'POST', description: 'Dokument erstellen' },
|
||||
{ path: '/documents/{id}', method: 'GET', description: 'Dokument Details' },
|
||||
{ path: '/documents/{id}', method: 'PUT', description: 'Dokument aktualisieren' },
|
||||
{ path: '/documents/{id}', method: 'DELETE', description: 'Dokument loeschen' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/sdk/consent-management',
|
||||
oldAdminPage: '/admin/consent',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'critical'
|
||||
},
|
||||
{
|
||||
id: 'consent-versions',
|
||||
name: 'Dokument-Versionierung',
|
||||
description: 'Versionsverwaltung und Freigabe-Workflow fuer rechtliche Dokumente',
|
||||
category: 'compliance',
|
||||
backend: {
|
||||
service: 'consent-service',
|
||||
port: 8081,
|
||||
basePath: '/api/consent/admin',
|
||||
endpoints: [
|
||||
{ path: '/documents/{id}/versions', method: 'GET', description: 'Versionen eines Dokuments' },
|
||||
{ path: '/versions', method: 'POST', description: 'Neue Version erstellen' },
|
||||
{ path: '/versions/{id}', method: 'PUT', description: 'Version aktualisieren' },
|
||||
{ path: '/versions/{id}', method: 'DELETE', description: 'Version loeschen' },
|
||||
{ path: '/versions/{id}/submit-review', method: 'POST', description: 'Zur Pruefung einreichen' },
|
||||
{ path: '/versions/{id}/approve', method: 'POST', description: 'Version genehmigen' },
|
||||
{ path: '/versions/{id}/reject', method: 'POST', description: 'Version ablehnen' },
|
||||
{ path: '/versions/{id}/publish', method: 'POST', description: 'Version veroeffentlichen' },
|
||||
{ path: '/versions/{id}/approval-history', method: 'GET', description: 'Genehmigungsverlauf' },
|
||||
{ path: '/versions/upload-word', method: 'POST', description: 'Word-Dokument importieren' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/sdk/workflow',
|
||||
oldAdminPage: '/admin/consent (Versions Tab)',
|
||||
status: 'connected'
|
||||
},
|
||||
dependencies: ['consent-documents'],
|
||||
priority: 'critical'
|
||||
},
|
||||
{
|
||||
id: 'consent-user',
|
||||
name: 'Nutzer-Einwilligungen',
|
||||
description: 'Tracking von Nutzer-Einwilligungen fuer DSGVO-Compliance',
|
||||
category: 'compliance',
|
||||
backend: {
|
||||
service: 'consent-service',
|
||||
port: 8081,
|
||||
basePath: '/api/consent',
|
||||
endpoints: [
|
||||
{ path: '/status', method: 'GET', description: 'Einwilligungsstatus pruefen' },
|
||||
{ path: '/give', method: 'POST', description: 'Einwilligung erteilen' },
|
||||
{ path: '/withdraw', method: 'POST', description: 'Einwilligung widerrufen' },
|
||||
{ path: '/history', method: 'GET', description: 'Einwilligungshistorie' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/sdk/einwilligungen',
|
||||
oldAdminPage: '/admin/consent (Users Tab)',
|
||||
status: 'connected',
|
||||
},
|
||||
priority: 'critical',
|
||||
},
|
||||
{
|
||||
id: 'dsr-requests',
|
||||
name: 'Datenschutzanfragen (DSR)',
|
||||
description: 'DSGVO Art. 15-21 Anfragen verwalten',
|
||||
category: 'compliance',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/dsr',
|
||||
endpoints: [
|
||||
{ path: '/requests', method: 'GET', description: 'Alle DSR-Anfragen' },
|
||||
{ path: '/requests', method: 'POST', description: 'Neue Anfrage erstellen' },
|
||||
{ path: '/requests/{id}', method: 'GET', description: 'Anfrage-Details' },
|
||||
{ path: '/requests/{id}/process', method: 'POST', description: 'Anfrage bearbeiten' },
|
||||
{ path: '/requests/{id}/export', method: 'GET', description: 'Daten exportieren' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/sdk/dsr',
|
||||
oldAdminPage: '/admin/dsr',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'high',
|
||||
},
|
||||
{
|
||||
id: 'dsms',
|
||||
name: 'Datenschutz-Management-System',
|
||||
description: 'Zentrales DSMS fuer Dokumentation und Compliance',
|
||||
category: 'compliance',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/dsms',
|
||||
endpoints: [
|
||||
{ path: '/documents', method: 'GET', description: 'DSMS-Dokumente' },
|
||||
{ path: '/processes', method: 'GET', description: 'Verarbeitungsverzeichnis' },
|
||||
{ path: '/toms', method: 'GET', description: 'TOM-Katalog' },
|
||||
{ path: '/audits', method: 'GET', description: 'Audit-Historie' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/sdk/dsms',
|
||||
oldAdminPage: '/admin/dsms',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
{
|
||||
id: 'cookie-categories',
|
||||
name: 'Cookie-Kategorien',
|
||||
description: 'Verwaltung von Cookie-Kategorien fuer Consent Banner',
|
||||
category: 'compliance',
|
||||
backend: {
|
||||
service: 'consent-service',
|
||||
port: 8081,
|
||||
basePath: '/api/consent/admin',
|
||||
endpoints: [
|
||||
{ path: '/cookies/categories', method: 'GET', description: 'Alle Cookie-Kategorien' },
|
||||
{ path: '/cookies/categories', method: 'POST', description: 'Kategorie erstellen' },
|
||||
{ path: '/cookies/categories/{id}', method: 'PUT', description: 'Kategorie aktualisieren' },
|
||||
{ path: '/cookies/categories/{id}', method: 'DELETE', description: 'Kategorie loeschen' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: undefined,
|
||||
oldAdminPage: '/admin/consent (Cookies Tab)',
|
||||
status: 'not-connected'
|
||||
},
|
||||
priority: 'medium',
|
||||
notes: 'Cookie-Kategorien Tab im alten Admin vorhanden'
|
||||
},
|
||||
|
||||
// ===========================================
|
||||
// AI MODULES
|
||||
// ===========================================
|
||||
{
|
||||
id: 'ai-agents',
|
||||
name: 'AI Agents',
|
||||
description: 'Multi-Agent System Verwaltung und Monitoring',
|
||||
category: 'ai',
|
||||
backend: {
|
||||
service: 'voice-service',
|
||||
port: 8088,
|
||||
basePath: '/api/v1/agents',
|
||||
endpoints: [
|
||||
{ path: '/sessions', method: 'GET', description: 'Agent-Sessions' },
|
||||
{ path: '/statistics', method: 'GET', description: 'Agent-Statistiken' },
|
||||
{ path: '/{agentId}', method: 'GET', description: 'Agent-Details' },
|
||||
{ path: '/{agentId}/soul', method: 'GET', description: 'SOUL-Konfiguration' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/ai/agents',
|
||||
oldAdminPage: undefined,
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'high',
|
||||
notes: 'Neues Multi-Agent System'
|
||||
},
|
||||
{
|
||||
id: 'ai-quality',
|
||||
name: 'AI Quality (BQAS)',
|
||||
description: 'KI-Qualitaetssicherung und Evaluierung',
|
||||
category: 'ai',
|
||||
backend: {
|
||||
service: 'voice-service',
|
||||
port: 8088,
|
||||
basePath: '/api/bqas',
|
||||
endpoints: [
|
||||
{ path: '/evaluate', method: 'POST', description: 'Antwort evaluieren' },
|
||||
{ path: '/metrics', method: 'GET', description: 'Qualitaetsmetriken' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/ai/quality',
|
||||
oldAdminPage: '/admin/quality',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'high'
|
||||
},
|
||||
{
|
||||
id: 'magic-help',
|
||||
name: 'Magic Help (TrOCR)',
|
||||
description: 'Handschrifterkennung mit TrOCR und LoRA Fine-Tuning',
|
||||
category: 'ai',
|
||||
backend: {
|
||||
service: 'klausur-service',
|
||||
port: 8086,
|
||||
basePath: '/api/klausur/trocr',
|
||||
endpoints: [
|
||||
{ path: '/status', method: 'GET', description: 'TrOCR Status' },
|
||||
{ path: '/extract', method: 'POST', description: 'Text aus Bild extrahieren' },
|
||||
{ path: '/training/examples', method: 'GET', description: 'Trainingsbeispiele' },
|
||||
{ path: '/training/add', method: 'POST', description: 'Trainingsbeispiel hinzufuegen' },
|
||||
{ path: '/training/fine-tune', method: 'POST', description: 'Fine-Tuning starten' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/ai/magic-help',
|
||||
oldAdminPage: '/admin/magic-help',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium',
|
||||
notes: 'Lokale Handschrifterkennung mit Privacy-by-Design'
|
||||
},
|
||||
{
|
||||
id: 'klausur-korrektur',
|
||||
name: 'Klausur-Korrektur',
|
||||
description: 'KI-gestuetzte Abitur-Korrektur mit EH-Vorschlaegen',
|
||||
category: 'ai',
|
||||
backend: {
|
||||
service: 'klausur-service',
|
||||
port: 8086,
|
||||
basePath: '/api/v1',
|
||||
endpoints: [
|
||||
{ path: '/klausuren', method: 'GET', description: 'Alle Klausuren' },
|
||||
{ path: '/klausuren', method: 'POST', description: 'Klausur erstellen' },
|
||||
{ path: '/klausuren/{id}/students', method: 'GET', description: 'Studentenarbeiten' },
|
||||
{ path: '/students/{id}/annotations', method: 'GET', description: 'Anmerkungen' },
|
||||
{ path: '/students/{id}/gutachten/generate', method: 'POST', description: 'Gutachten generieren' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/ai/klausur-korrektur',
|
||||
oldAdminPage: '/admin/klausur-korrektur',
|
||||
status: 'not-connected'
|
||||
},
|
||||
priority: 'high',
|
||||
notes: 'Komplexes Modul mit eigenem Backend-Service'
|
||||
},
|
||||
{
|
||||
id: 'ocr-labeling',
|
||||
name: 'OCR-Labeling',
|
||||
description: 'Handschrift-Training und Label-Verwaltung',
|
||||
category: 'ai',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/ocr',
|
||||
endpoints: [
|
||||
{ path: '/samples', method: 'GET', description: 'Training-Samples' },
|
||||
{ path: '/labels', method: 'GET', description: 'Label-Kategorien' },
|
||||
{ path: '/train', method: 'POST', description: 'Training starten' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/ai/ocr-labeling',
|
||||
oldAdminPage: '/admin/ocr-labeling',
|
||||
status: 'not-connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
{
|
||||
id: 'rag-management',
|
||||
name: 'RAG & Daten',
|
||||
description: 'Retrieval Augmented Generation und Training Data',
|
||||
category: 'ai',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/rag',
|
||||
endpoints: [
|
||||
{ path: '/documents', method: 'GET', description: 'RAG-Dokumente' },
|
||||
{ path: '/collections', method: 'GET', description: 'Vector-Collections' },
|
||||
{ path: '/query', method: 'POST', description: 'RAG-Abfrage' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/ai/rag',
|
||||
oldAdminPage: '/admin/rag',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
|
||||
// ===========================================
|
||||
// INFRASTRUCTURE MODULES
|
||||
// ===========================================
|
||||
{
|
||||
id: 'gpu-infrastructure',
|
||||
name: 'GPU Infrastruktur',
|
||||
description: 'vast.ai GPU-Management und Monitoring',
|
||||
category: 'infrastructure',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/gpu',
|
||||
endpoints: [
|
||||
{ path: '/instances', method: 'GET', description: 'GPU-Instanzen' },
|
||||
{ path: '/instances', method: 'POST', description: 'Instanz erstellen' },
|
||||
{ path: '/usage', method: 'GET', description: 'Nutzungsstatistiken' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/infrastructure/gpu',
|
||||
oldAdminPage: '/admin/gpu',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
{
|
||||
id: 'security-dashboard',
|
||||
name: 'Security Dashboard',
|
||||
description: 'DevSecOps Dashboard und Vulnerability Scans',
|
||||
category: 'infrastructure',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/security',
|
||||
endpoints: [
|
||||
{ path: '/scans', method: 'GET', description: 'Security-Scans' },
|
||||
{ path: '/vulnerabilities', method: 'GET', description: 'Schwachstellen' },
|
||||
{ path: '/compliance', method: 'GET', description: 'Compliance-Status' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/infrastructure/security',
|
||||
oldAdminPage: '/admin/security',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'high'
|
||||
},
|
||||
{
|
||||
id: 'sbom',
|
||||
name: 'SBOM',
|
||||
description: 'Software Bill of Materials',
|
||||
category: 'infrastructure',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/sbom',
|
||||
endpoints: [
|
||||
{ path: '/components', method: 'GET', description: 'Komponenten-Liste' },
|
||||
{ path: '/licenses', method: 'GET', description: 'Lizenz-Uebersicht' },
|
||||
{ path: '/export', method: 'GET', description: 'SBOM exportieren' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/infrastructure/sbom',
|
||||
oldAdminPage: '/admin/sbom',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
|
||||
{
|
||||
id: 'middleware',
|
||||
name: 'Middleware Manager',
|
||||
description: 'Verwaltung und Monitoring der Backend-Middleware',
|
||||
category: 'infrastructure',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/middleware',
|
||||
endpoints: [
|
||||
{ path: '/status', method: 'GET', description: 'Middleware-Status' },
|
||||
{ path: '/config', method: 'GET', description: 'Konfiguration' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/infrastructure/middleware',
|
||||
oldAdminPage: '/admin/middleware',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
{
|
||||
id: 'ci-cd',
|
||||
name: 'CI/CD Pipeline',
|
||||
description: 'Build-Pipeline und Deployment-Management',
|
||||
category: 'infrastructure',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/builds',
|
||||
endpoints: [
|
||||
{ path: '/pipelines', method: 'GET', description: 'Pipeline-Status' },
|
||||
{ path: '/builds', method: 'GET', description: 'Build-Historie' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/infrastructure/ci-cd',
|
||||
oldAdminPage: '/admin/builds',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
|
||||
// ===========================================
|
||||
// EDUCATION MODULES
|
||||
// ===========================================
|
||||
{
|
||||
id: 'edu-search',
|
||||
name: 'Bildungssuche',
|
||||
description: 'Suche nach Bildungsinhalten und Ressourcen',
|
||||
category: 'education',
|
||||
backend: {
|
||||
service: 'edu-search-service',
|
||||
port: 8089,
|
||||
basePath: '/api/edu',
|
||||
endpoints: [
|
||||
{ path: '/search', method: 'GET', description: 'Bildungssuche' },
|
||||
{ path: '/resources', method: 'GET', description: 'Ressourcen' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/education/edu-search',
|
||||
oldAdminPage: '/admin/edu-search',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
|
||||
// ===========================================
|
||||
// COMMUNICATION MODULES
|
||||
// ===========================================
|
||||
{
|
||||
id: 'alerts',
|
||||
name: 'Alerts & Benachrichtigungen',
|
||||
description: 'System-Benachrichtigungen und Alerts',
|
||||
category: 'communication',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/alerts',
|
||||
endpoints: [
|
||||
{ path: '/notifications', method: 'GET', description: 'Benachrichtigungen' },
|
||||
{ path: '/alerts', method: 'GET', description: 'Aktive Alerts' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/communication/alerts',
|
||||
oldAdminPage: '/admin/alerts',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
{
|
||||
id: 'unified-inbox',
|
||||
name: 'Unified Inbox',
|
||||
description: 'E-Mail-Konten und KI-Analyse',
|
||||
category: 'communication',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/mail',
|
||||
endpoints: [
|
||||
{ path: '/accounts', method: 'GET', description: 'E-Mail-Konten' },
|
||||
{ path: '/messages', method: 'GET', description: 'Nachrichten' },
|
||||
{ path: '/analyze', method: 'POST', description: 'KI-Analyse' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/communication/mail',
|
||||
oldAdminPage: '/admin/mail',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'low'
|
||||
},
|
||||
|
||||
// ===========================================
|
||||
// DEVELOPMENT MODULES
|
||||
// ===========================================
|
||||
{
|
||||
id: 'voice-service',
|
||||
name: 'Voice Service',
|
||||
description: 'Voice-First Interface',
|
||||
category: 'development',
|
||||
backend: {
|
||||
service: 'voice-service',
|
||||
port: 8088,
|
||||
basePath: '/api/voice',
|
||||
endpoints: [
|
||||
{ path: '/transcribe', method: 'POST', description: 'Sprache transkribieren' },
|
||||
{ path: '/synthesize', method: 'POST', description: 'Text zu Sprache' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/development/voice',
|
||||
oldAdminPage: '/admin/voice',
|
||||
status: 'not-connected'
|
||||
},
|
||||
priority: 'low'
|
||||
},
|
||||
]
|
||||
@@ -6,6 +6,9 @@
|
||||
* - Backend service and endpoints
|
||||
* - Frontend pages that use it
|
||||
* - Connection status (connected, partial, not connected)
|
||||
*
|
||||
* Module definitions live in ./module-registry-data.ts
|
||||
* This file exports the type, re-exports the data, and provides helper functions.
|
||||
*/
|
||||
|
||||
export interface BackendModule {
|
||||
@@ -33,512 +36,11 @@ export interface BackendModule {
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export const MODULE_REGISTRY: BackendModule[] = [
|
||||
// ===========================================
|
||||
// COMPLIANCE MODULES
|
||||
// ===========================================
|
||||
{
|
||||
id: 'consent-documents',
|
||||
name: 'Consent Dokumente',
|
||||
description: 'Verwaltung rechtlicher Dokumente (AGB, Datenschutz, etc.)',
|
||||
category: 'compliance',
|
||||
backend: {
|
||||
service: 'consent-service',
|
||||
port: 8081,
|
||||
basePath: '/api/consent/admin',
|
||||
endpoints: [
|
||||
{ path: '/documents', method: 'GET', description: 'Liste aller Dokumente' },
|
||||
{ path: '/documents', method: 'POST', description: 'Dokument erstellen' },
|
||||
{ path: '/documents/{id}', method: 'GET', description: 'Dokument Details' },
|
||||
{ path: '/documents/{id}', method: 'PUT', description: 'Dokument aktualisieren' },
|
||||
{ path: '/documents/{id}', method: 'DELETE', description: 'Dokument loeschen' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/sdk/consent-management',
|
||||
oldAdminPage: '/admin/consent',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'critical'
|
||||
},
|
||||
{
|
||||
id: 'consent-versions',
|
||||
name: 'Dokument-Versionierung',
|
||||
description: 'Versionsverwaltung und Freigabe-Workflow fuer rechtliche Dokumente',
|
||||
category: 'compliance',
|
||||
backend: {
|
||||
service: 'consent-service',
|
||||
port: 8081,
|
||||
basePath: '/api/consent/admin',
|
||||
endpoints: [
|
||||
{ path: '/documents/{id}/versions', method: 'GET', description: 'Versionen eines Dokuments' },
|
||||
{ path: '/versions', method: 'POST', description: 'Neue Version erstellen' },
|
||||
{ path: '/versions/{id}', method: 'PUT', description: 'Version aktualisieren' },
|
||||
{ path: '/versions/{id}', method: 'DELETE', description: 'Version loeschen' },
|
||||
{ path: '/versions/{id}/submit-review', method: 'POST', description: 'Zur Pruefung einreichen' },
|
||||
{ path: '/versions/{id}/approve', method: 'POST', description: 'Version genehmigen' },
|
||||
{ path: '/versions/{id}/reject', method: 'POST', description: 'Version ablehnen' },
|
||||
{ path: '/versions/{id}/publish', method: 'POST', description: 'Version veroeffentlichen' },
|
||||
{ path: '/versions/{id}/approval-history', method: 'GET', description: 'Genehmigungsverlauf' },
|
||||
{ path: '/versions/upload-word', method: 'POST', description: 'Word-Dokument importieren' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/sdk/workflow',
|
||||
oldAdminPage: '/admin/consent (Versions Tab)',
|
||||
status: 'connected'
|
||||
},
|
||||
dependencies: ['consent-documents'],
|
||||
priority: 'critical'
|
||||
},
|
||||
{
|
||||
id: 'consent-user',
|
||||
name: 'Nutzer-Einwilligungen',
|
||||
description: 'Tracking von Nutzer-Einwilligungen fuer DSGVO-Compliance',
|
||||
category: 'compliance',
|
||||
backend: {
|
||||
service: 'consent-service',
|
||||
port: 8081,
|
||||
basePath: '/api/consent',
|
||||
endpoints: [
|
||||
{ path: '/status', method: 'GET', description: 'Einwilligungsstatus pruefen' },
|
||||
{ path: '/give', method: 'POST', description: 'Einwilligung erteilen' },
|
||||
{ path: '/withdraw', method: 'POST', description: 'Einwilligung widerrufen' },
|
||||
{ path: '/history', method: 'GET', description: 'Einwilligungshistorie' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/sdk/einwilligungen',
|
||||
oldAdminPage: '/admin/consent (Users Tab)',
|
||||
status: 'connected',
|
||||
},
|
||||
priority: 'critical',
|
||||
},
|
||||
{
|
||||
id: 'dsr-requests',
|
||||
name: 'Datenschutzanfragen (DSR)',
|
||||
description: 'DSGVO Art. 15-21 Anfragen verwalten',
|
||||
category: 'compliance',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/dsr',
|
||||
endpoints: [
|
||||
{ path: '/requests', method: 'GET', description: 'Alle DSR-Anfragen' },
|
||||
{ path: '/requests', method: 'POST', description: 'Neue Anfrage erstellen' },
|
||||
{ path: '/requests/{id}', method: 'GET', description: 'Anfrage-Details' },
|
||||
{ path: '/requests/{id}/process', method: 'POST', description: 'Anfrage bearbeiten' },
|
||||
{ path: '/requests/{id}/export', method: 'GET', description: 'Daten exportieren' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/sdk/dsr',
|
||||
oldAdminPage: '/admin/dsr',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'high',
|
||||
},
|
||||
{
|
||||
id: 'dsms',
|
||||
name: 'Datenschutz-Management-System',
|
||||
description: 'Zentrales DSMS fuer Dokumentation und Compliance',
|
||||
category: 'compliance',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/dsms',
|
||||
endpoints: [
|
||||
{ path: '/documents', method: 'GET', description: 'DSMS-Dokumente' },
|
||||
{ path: '/processes', method: 'GET', description: 'Verarbeitungsverzeichnis' },
|
||||
{ path: '/toms', method: 'GET', description: 'TOM-Katalog' },
|
||||
{ path: '/audits', method: 'GET', description: 'Audit-Historie' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/sdk/dsms',
|
||||
oldAdminPage: '/admin/dsms',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
{
|
||||
id: 'cookie-categories',
|
||||
name: 'Cookie-Kategorien',
|
||||
description: 'Verwaltung von Cookie-Kategorien fuer Consent Banner',
|
||||
category: 'compliance',
|
||||
backend: {
|
||||
service: 'consent-service',
|
||||
port: 8081,
|
||||
basePath: '/api/consent/admin',
|
||||
endpoints: [
|
||||
{ path: '/cookies/categories', method: 'GET', description: 'Alle Cookie-Kategorien' },
|
||||
{ path: '/cookies/categories', method: 'POST', description: 'Kategorie erstellen' },
|
||||
{ path: '/cookies/categories/{id}', method: 'PUT', description: 'Kategorie aktualisieren' },
|
||||
{ path: '/cookies/categories/{id}', method: 'DELETE', description: 'Kategorie loeschen' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: undefined,
|
||||
oldAdminPage: '/admin/consent (Cookies Tab)',
|
||||
status: 'not-connected'
|
||||
},
|
||||
priority: 'medium',
|
||||
notes: 'Cookie-Kategorien Tab im alten Admin vorhanden'
|
||||
},
|
||||
// Re-export data from the data file
|
||||
export { MODULE_REGISTRY } from './module-registry-data'
|
||||
|
||||
// ===========================================
|
||||
// AI MODULES
|
||||
// ===========================================
|
||||
{
|
||||
id: 'ai-agents',
|
||||
name: 'AI Agents',
|
||||
description: 'Multi-Agent System Verwaltung und Monitoring',
|
||||
category: 'ai',
|
||||
backend: {
|
||||
service: 'voice-service',
|
||||
port: 8088,
|
||||
basePath: '/api/v1/agents',
|
||||
endpoints: [
|
||||
{ path: '/sessions', method: 'GET', description: 'Agent-Sessions' },
|
||||
{ path: '/statistics', method: 'GET', description: 'Agent-Statistiken' },
|
||||
{ path: '/{agentId}', method: 'GET', description: 'Agent-Details' },
|
||||
{ path: '/{agentId}/soul', method: 'GET', description: 'SOUL-Konfiguration' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/ai/agents',
|
||||
oldAdminPage: undefined,
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'high',
|
||||
notes: 'Neues Multi-Agent System'
|
||||
},
|
||||
{
|
||||
id: 'ai-quality',
|
||||
name: 'AI Quality (BQAS)',
|
||||
description: 'KI-Qualitaetssicherung und Evaluierung',
|
||||
category: 'ai',
|
||||
backend: {
|
||||
service: 'voice-service',
|
||||
port: 8088,
|
||||
basePath: '/api/bqas',
|
||||
endpoints: [
|
||||
{ path: '/evaluate', method: 'POST', description: 'Antwort evaluieren' },
|
||||
{ path: '/metrics', method: 'GET', description: 'Qualitaetsmetriken' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/ai/quality',
|
||||
oldAdminPage: '/admin/quality',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'high'
|
||||
},
|
||||
{
|
||||
id: 'magic-help',
|
||||
name: 'Magic Help (TrOCR)',
|
||||
description: 'Handschrifterkennung mit TrOCR und LoRA Fine-Tuning',
|
||||
category: 'ai',
|
||||
backend: {
|
||||
service: 'klausur-service',
|
||||
port: 8086,
|
||||
basePath: '/api/klausur/trocr',
|
||||
endpoints: [
|
||||
{ path: '/status', method: 'GET', description: 'TrOCR Status' },
|
||||
{ path: '/extract', method: 'POST', description: 'Text aus Bild extrahieren' },
|
||||
{ path: '/training/examples', method: 'GET', description: 'Trainingsbeispiele' },
|
||||
{ path: '/training/add', method: 'POST', description: 'Trainingsbeispiel hinzufuegen' },
|
||||
{ path: '/training/fine-tune', method: 'POST', description: 'Fine-Tuning starten' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/ai/magic-help',
|
||||
oldAdminPage: '/admin/magic-help',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium',
|
||||
notes: 'Lokale Handschrifterkennung mit Privacy-by-Design'
|
||||
},
|
||||
{
|
||||
id: 'klausur-korrektur',
|
||||
name: 'Klausur-Korrektur',
|
||||
description: 'KI-gestuetzte Abitur-Korrektur mit EH-Vorschlaegen',
|
||||
category: 'ai',
|
||||
backend: {
|
||||
service: 'klausur-service',
|
||||
port: 8086,
|
||||
basePath: '/api/v1',
|
||||
endpoints: [
|
||||
{ path: '/klausuren', method: 'GET', description: 'Alle Klausuren' },
|
||||
{ path: '/klausuren', method: 'POST', description: 'Klausur erstellen' },
|
||||
{ path: '/klausuren/{id}/students', method: 'GET', description: 'Studentenarbeiten' },
|
||||
{ path: '/students/{id}/annotations', method: 'GET', description: 'Anmerkungen' },
|
||||
{ path: '/students/{id}/gutachten/generate', method: 'POST', description: 'Gutachten generieren' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/ai/klausur-korrektur',
|
||||
oldAdminPage: '/admin/klausur-korrektur',
|
||||
status: 'not-connected'
|
||||
},
|
||||
priority: 'high',
|
||||
notes: 'Komplexes Modul mit eigenem Backend-Service'
|
||||
},
|
||||
{
|
||||
id: 'ocr-labeling',
|
||||
name: 'OCR-Labeling',
|
||||
description: 'Handschrift-Training und Label-Verwaltung',
|
||||
category: 'ai',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/ocr',
|
||||
endpoints: [
|
||||
{ path: '/samples', method: 'GET', description: 'Training-Samples' },
|
||||
{ path: '/labels', method: 'GET', description: 'Label-Kategorien' },
|
||||
{ path: '/train', method: 'POST', description: 'Training starten' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/ai/ocr-labeling',
|
||||
oldAdminPage: '/admin/ocr-labeling',
|
||||
status: 'not-connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
{
|
||||
id: 'rag-management',
|
||||
name: 'RAG & Daten',
|
||||
description: 'Retrieval Augmented Generation und Training Data',
|
||||
category: 'ai',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/rag',
|
||||
endpoints: [
|
||||
{ path: '/documents', method: 'GET', description: 'RAG-Dokumente' },
|
||||
{ path: '/collections', method: 'GET', description: 'Vector-Collections' },
|
||||
{ path: '/query', method: 'POST', description: 'RAG-Abfrage' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/ai/rag',
|
||||
oldAdminPage: '/admin/rag',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
|
||||
// ===========================================
|
||||
// INFRASTRUCTURE MODULES
|
||||
// ===========================================
|
||||
{
|
||||
id: 'gpu-infrastructure',
|
||||
name: 'GPU Infrastruktur',
|
||||
description: 'vast.ai GPU-Management und Monitoring',
|
||||
category: 'infrastructure',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/gpu',
|
||||
endpoints: [
|
||||
{ path: '/instances', method: 'GET', description: 'GPU-Instanzen' },
|
||||
{ path: '/instances', method: 'POST', description: 'Instanz erstellen' },
|
||||
{ path: '/usage', method: 'GET', description: 'Nutzungsstatistiken' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/infrastructure/gpu',
|
||||
oldAdminPage: '/admin/gpu',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
{
|
||||
id: 'security-dashboard',
|
||||
name: 'Security Dashboard',
|
||||
description: 'DevSecOps Dashboard und Vulnerability Scans',
|
||||
category: 'infrastructure',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/security',
|
||||
endpoints: [
|
||||
{ path: '/scans', method: 'GET', description: 'Security-Scans' },
|
||||
{ path: '/vulnerabilities', method: 'GET', description: 'Schwachstellen' },
|
||||
{ path: '/compliance', method: 'GET', description: 'Compliance-Status' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/infrastructure/security',
|
||||
oldAdminPage: '/admin/security',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'high'
|
||||
},
|
||||
{
|
||||
id: 'sbom',
|
||||
name: 'SBOM',
|
||||
description: 'Software Bill of Materials',
|
||||
category: 'infrastructure',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/sbom',
|
||||
endpoints: [
|
||||
{ path: '/components', method: 'GET', description: 'Komponenten-Liste' },
|
||||
{ path: '/licenses', method: 'GET', description: 'Lizenz-Uebersicht' },
|
||||
{ path: '/export', method: 'GET', description: 'SBOM exportieren' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/infrastructure/sbom',
|
||||
oldAdminPage: '/admin/sbom',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
|
||||
{
|
||||
id: 'middleware',
|
||||
name: 'Middleware Manager',
|
||||
description: 'Verwaltung und Monitoring der Backend-Middleware',
|
||||
category: 'infrastructure',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/middleware',
|
||||
endpoints: [
|
||||
{ path: '/status', method: 'GET', description: 'Middleware-Status' },
|
||||
{ path: '/config', method: 'GET', description: 'Konfiguration' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/infrastructure/middleware',
|
||||
oldAdminPage: '/admin/middleware',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
{
|
||||
id: 'ci-cd',
|
||||
name: 'CI/CD Pipeline',
|
||||
description: 'Build-Pipeline und Deployment-Management',
|
||||
category: 'infrastructure',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/builds',
|
||||
endpoints: [
|
||||
{ path: '/pipelines', method: 'GET', description: 'Pipeline-Status' },
|
||||
{ path: '/builds', method: 'GET', description: 'Build-Historie' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/infrastructure/ci-cd',
|
||||
oldAdminPage: '/admin/builds',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
|
||||
// ===========================================
|
||||
// EDUCATION MODULES
|
||||
// ===========================================
|
||||
{
|
||||
id: 'edu-search',
|
||||
name: 'Bildungssuche',
|
||||
description: 'Suche nach Bildungsinhalten und Ressourcen',
|
||||
category: 'education',
|
||||
backend: {
|
||||
service: 'edu-search-service',
|
||||
port: 8089,
|
||||
basePath: '/api/edu',
|
||||
endpoints: [
|
||||
{ path: '/search', method: 'GET', description: 'Bildungssuche' },
|
||||
{ path: '/resources', method: 'GET', description: 'Ressourcen' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/education/edu-search',
|
||||
oldAdminPage: '/admin/edu-search',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
|
||||
// ===========================================
|
||||
// COMMUNICATION MODULES
|
||||
// ===========================================
|
||||
{
|
||||
id: 'alerts',
|
||||
name: 'Alerts & Benachrichtigungen',
|
||||
description: 'System-Benachrichtigungen und Alerts',
|
||||
category: 'communication',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/alerts',
|
||||
endpoints: [
|
||||
{ path: '/notifications', method: 'GET', description: 'Benachrichtigungen' },
|
||||
{ path: '/alerts', method: 'GET', description: 'Aktive Alerts' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/communication/alerts',
|
||||
oldAdminPage: '/admin/alerts',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'medium'
|
||||
},
|
||||
{
|
||||
id: 'unified-inbox',
|
||||
name: 'Unified Inbox',
|
||||
description: 'E-Mail-Konten und KI-Analyse',
|
||||
category: 'communication',
|
||||
backend: {
|
||||
service: 'python-backend',
|
||||
port: 8000,
|
||||
basePath: '/api/mail',
|
||||
endpoints: [
|
||||
{ path: '/accounts', method: 'GET', description: 'E-Mail-Konten' },
|
||||
{ path: '/messages', method: 'GET', description: 'Nachrichten' },
|
||||
{ path: '/analyze', method: 'POST', description: 'KI-Analyse' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/communication/mail',
|
||||
oldAdminPage: '/admin/mail',
|
||||
status: 'connected'
|
||||
},
|
||||
priority: 'low'
|
||||
},
|
||||
|
||||
// ===========================================
|
||||
// DEVELOPMENT MODULES
|
||||
// ===========================================
|
||||
{
|
||||
id: 'voice-service',
|
||||
name: 'Voice Service',
|
||||
description: 'Voice-First Interface',
|
||||
category: 'development',
|
||||
backend: {
|
||||
service: 'voice-service',
|
||||
port: 8088,
|
||||
basePath: '/api/voice',
|
||||
endpoints: [
|
||||
{ path: '/transcribe', method: 'POST', description: 'Sprache transkribieren' },
|
||||
{ path: '/synthesize', method: 'POST', description: 'Text zu Sprache' },
|
||||
]
|
||||
},
|
||||
frontend: {
|
||||
adminV2Page: '/development/voice',
|
||||
oldAdminPage: '/admin/voice',
|
||||
status: 'not-connected'
|
||||
},
|
||||
priority: 'low'
|
||||
},
|
||||
]
|
||||
// Import for use in helper functions
|
||||
import { MODULE_REGISTRY } from './module-registry-data'
|
||||
|
||||
// Helper functions
|
||||
export function getModulesByCategory(category: BackendModule['category']): BackendModule[] {
|
||||
|
||||
488
admin-lehrer/lib/sdk/catalog-manager/catalog-registry-data.ts
Normal file
488
admin-lehrer/lib/sdk/catalog-manager/catalog-registry-data.ts
Normal file
@@ -0,0 +1,488 @@
|
||||
/**
|
||||
* SDK Catalog Manager - Registry Data
|
||||
*
|
||||
* Contains CATALOG_REGISTRY definitions and SYSTEM_ENTRIES_MAP.
|
||||
* Separated from catalog-registry.ts to keep each file under 500 LOC.
|
||||
*/
|
||||
import type { CatalogId, CatalogMeta } from './types'
|
||||
|
||||
import { RISK_CATALOG } from '../dsfa/risk-catalog'
|
||||
import { MITIGATION_LIBRARY } from '../dsfa/mitigation-library'
|
||||
import { AI_RISK_CATALOG } from '../dsfa/ai-risk-catalog'
|
||||
import { AI_MITIGATION_LIBRARY } from '../dsfa/ai-mitigation-library'
|
||||
import { PROHIBITED_AI_PRACTICES } from '../dsfa/prohibited-ai-practices'
|
||||
import { EU_BASE_FRAMEWORKS, NATIONAL_FRAMEWORKS } from '../dsfa/eu-legal-frameworks'
|
||||
import { GDPR_ENFORCEMENT_CASES } from '../dsfa/gdpr-enforcement-cases'
|
||||
import { WP248_CRITERIA, SDM_GOALS, DSFA_AUTHORITY_RESOURCES } from '../dsfa/types'
|
||||
import { VVT_BASELINE_CATALOG } from '../vvt-baseline-catalog'
|
||||
import { BASELINE_TEMPLATES } from '../loeschfristen-baseline-catalog'
|
||||
import { VENDOR_TEMPLATES, COUNTRY_RISK_PROFILES } from '../vendor-compliance/catalog/vendor-templates'
|
||||
import { LEGAL_BASIS_INFO, STANDARD_RETENTION_PERIODS } from '../vendor-compliance/catalog/legal-basis'
|
||||
|
||||
// SDM_GOALS as entries array (it's a Record, not an array)
|
||||
const SDM_GOALS_ENTRIES = Object.entries(SDM_GOALS).map(([key, val]) => ({
|
||||
id: key,
|
||||
name: val.name,
|
||||
description: val.description,
|
||||
article: val.article,
|
||||
}))
|
||||
|
||||
export const CATALOG_REGISTRY: Record<CatalogId, CatalogMeta> = {
|
||||
'dsfa-risks': {
|
||||
id: 'dsfa-risks',
|
||||
name: 'DSFA Risikokatalog',
|
||||
description: 'Standardrisiken fuer Datenschutz-Folgenabschaetzungen',
|
||||
module: 'dsfa',
|
||||
icon: 'ShieldAlert',
|
||||
systemCount: RISK_CATALOG.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'category',
|
||||
fields: [
|
||||
{ key: 'id', label: 'Risiko-ID', type: 'text', required: true, placeholder: 'R-XXX-01' },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'category', label: 'Kategorie', type: 'select', required: true, options: [
|
||||
{ value: 'confidentiality', label: 'Vertraulichkeit' },
|
||||
{ value: 'integrity', label: 'Integritaet' },
|
||||
{ value: 'availability', label: 'Verfuegbarkeit' },
|
||||
{ value: 'rights_freedoms', label: 'Rechte & Freiheiten' },
|
||||
]},
|
||||
{ key: 'typicalLikelihood', label: 'Typische Eintrittswahrscheinlichkeit', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
{ key: 'typicalImpact', label: 'Typische Auswirkung', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'category'],
|
||||
},
|
||||
|
||||
'dsfa-mitigations': {
|
||||
id: 'dsfa-mitigations',
|
||||
name: 'DSFA Massnahmenbibliothek',
|
||||
description: 'Technische und organisatorische Massnahmen fuer DSFAs',
|
||||
module: 'dsfa',
|
||||
icon: 'Shield',
|
||||
systemCount: MITIGATION_LIBRARY.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'type',
|
||||
fields: [
|
||||
{ key: 'id', label: 'Massnahmen-ID', type: 'text', required: true, placeholder: 'M-XXX-01' },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'type', label: 'Typ', type: 'select', required: true, options: [
|
||||
{ value: 'technical', label: 'Technisch' },
|
||||
{ value: 'organizational', label: 'Organisatorisch' },
|
||||
{ value: 'legal', label: 'Rechtlich' },
|
||||
]},
|
||||
{ key: 'effectiveness', label: 'Wirksamkeit', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'type', 'legalBasis'],
|
||||
},
|
||||
|
||||
'ai-risks': {
|
||||
id: 'ai-risks',
|
||||
name: 'KI-Risikokatalog',
|
||||
description: 'Spezifische Risiken fuer KI-Systeme',
|
||||
module: 'ai_act',
|
||||
icon: 'Bot',
|
||||
systemCount: AI_RISK_CATALOG.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'category',
|
||||
fields: [
|
||||
{ key: 'id', label: 'Risiko-ID', type: 'text', required: true, placeholder: 'R-AI-XXX-01' },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'category', label: 'Kategorie', type: 'select', required: true, options: [
|
||||
{ value: 'confidentiality', label: 'Vertraulichkeit' },
|
||||
{ value: 'integrity', label: 'Integritaet' },
|
||||
{ value: 'availability', label: 'Verfuegbarkeit' },
|
||||
{ value: 'rights_freedoms', label: 'Rechte & Freiheiten' },
|
||||
]},
|
||||
{ key: 'typicalLikelihood', label: 'Eintrittswahrscheinlichkeit', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
{ key: 'typicalImpact', label: 'Auswirkung', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'category'],
|
||||
},
|
||||
|
||||
'ai-mitigations': {
|
||||
id: 'ai-mitigations',
|
||||
name: 'KI-Massnahmenbibliothek',
|
||||
description: 'Massnahmen fuer KI-spezifische Risiken',
|
||||
module: 'ai_act',
|
||||
icon: 'ShieldCheck',
|
||||
systemCount: AI_MITIGATION_LIBRARY.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'type',
|
||||
fields: [
|
||||
{ key: 'id', label: 'Massnahmen-ID', type: 'text', required: true },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'type', label: 'Typ', type: 'select', required: true, options: [
|
||||
{ value: 'technical', label: 'Technisch' },
|
||||
{ value: 'organizational', label: 'Organisatorisch' },
|
||||
{ value: 'legal', label: 'Rechtlich' },
|
||||
]},
|
||||
{ key: 'effectiveness', label: 'Wirksamkeit', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'type'],
|
||||
},
|
||||
|
||||
'prohibited-ai-practices': {
|
||||
id: 'prohibited-ai-practices',
|
||||
name: 'Verbotene KI-Praktiken',
|
||||
description: 'Absolut und bedingt verbotene KI-Anwendungen nach AI Act',
|
||||
module: 'ai_act',
|
||||
icon: 'Ban',
|
||||
systemCount: PROHIBITED_AI_PRACTICES.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'severity',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'severity', label: 'Schwere', type: 'select', required: true, options: [
|
||||
{ value: 'absolute', label: 'Absolutes Verbot' },
|
||||
{ value: 'conditional', label: 'Bedingtes Verbot' },
|
||||
]},
|
||||
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'severity', 'legalBasis'],
|
||||
},
|
||||
|
||||
'vvt-templates': {
|
||||
id: 'vvt-templates',
|
||||
name: 'VVT Baseline-Vorlagen',
|
||||
description: 'Vorlagen fuer Verarbeitungstaetigkeiten',
|
||||
module: 'vvt',
|
||||
icon: 'FileText',
|
||||
systemCount: VVT_BASELINE_CATALOG.length,
|
||||
allowCustom: true,
|
||||
idField: 'templateId',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'businessFunction',
|
||||
fields: [
|
||||
{ key: 'templateId', label: 'Vorlagen-ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'businessFunction', label: 'Geschaeftsbereich', type: 'select', required: true, options: [
|
||||
{ value: 'hr', label: 'Personal' },
|
||||
{ value: 'finance', label: 'Finanzen' },
|
||||
{ value: 'sales', label: 'Vertrieb' },
|
||||
{ value: 'marketing', label: 'Marketing' },
|
||||
{ value: 'support', label: 'Support' },
|
||||
{ value: 'it', label: 'IT' },
|
||||
{ value: 'other', label: 'Sonstige' },
|
||||
]},
|
||||
{ key: 'protectionLevel', label: 'Schutzniveau', type: 'select', required: false, options: [
|
||||
{ value: 'LOW', label: 'Niedrig' },
|
||||
{ value: 'MEDIUM', label: 'Mittel' },
|
||||
{ value: 'HIGH', label: 'Hoch' },
|
||||
]},
|
||||
],
|
||||
searchableFields: ['templateId', 'name', 'description', 'businessFunction'],
|
||||
},
|
||||
|
||||
'loeschfristen-templates': {
|
||||
id: 'loeschfristen-templates',
|
||||
name: 'Loeschfristen-Vorlagen',
|
||||
description: 'Baseline-Vorlagen fuer Aufbewahrungsfristen',
|
||||
module: 'vvt',
|
||||
icon: 'Clock',
|
||||
systemCount: BASELINE_TEMPLATES.length,
|
||||
allowCustom: true,
|
||||
idField: 'templateId',
|
||||
nameField: 'dataObjectName',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'retentionDriver',
|
||||
fields: [
|
||||
{ key: 'templateId', label: 'Vorlagen-ID', type: 'text', required: true },
|
||||
{ key: 'dataObjectName', label: 'Datenobjekt', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'retentionDuration', label: 'Aufbewahrungsdauer', type: 'number', required: true, min: 0 },
|
||||
{ key: 'retentionUnit', label: 'Einheit', type: 'select', required: true, options: [
|
||||
{ value: 'days', label: 'Tage' },
|
||||
{ value: 'months', label: 'Monate' },
|
||||
{ value: 'years', label: 'Jahre' },
|
||||
]},
|
||||
{ key: 'deletionMethod', label: 'Loeschmethode', type: 'text', required: false },
|
||||
{ key: 'responsibleRole', label: 'Verantwortlich', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['templateId', 'dataObjectName', 'description', 'retentionDriver'],
|
||||
},
|
||||
|
||||
'vendor-templates': {
|
||||
id: 'vendor-templates',
|
||||
name: 'AV-Vorlagen',
|
||||
description: 'Vorlagen fuer Auftragsverarbeitungsvertraege',
|
||||
module: 'vendor',
|
||||
icon: 'Building2',
|
||||
systemCount: VENDOR_TEMPLATES.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'serviceCategory',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'serviceCategory', label: 'Kategorie', type: 'text', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'description', 'serviceCategory'],
|
||||
},
|
||||
|
||||
'country-risk-profiles': {
|
||||
id: 'country-risk-profiles',
|
||||
name: 'Laenderrisikoprofile',
|
||||
description: 'Datenschutz-Risikobewertung nach Laendern',
|
||||
module: 'vendor',
|
||||
icon: 'Globe',
|
||||
systemCount: COUNTRY_RISK_PROFILES.length,
|
||||
allowCustom: false,
|
||||
idField: 'code',
|
||||
nameField: 'name',
|
||||
categoryField: 'riskLevel',
|
||||
fields: [
|
||||
{ key: 'code', label: 'Laendercode', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Land', type: 'text', required: true },
|
||||
{ key: 'riskLevel', label: 'Risikostufe', type: 'select', required: true, options: [
|
||||
{ value: 'LOW', label: 'Niedrig' },
|
||||
{ value: 'MEDIUM', label: 'Mittel' },
|
||||
{ value: 'HIGH', label: 'Hoch' },
|
||||
{ value: 'VERY_HIGH', label: 'Sehr hoch' },
|
||||
]},
|
||||
{ key: 'isEU', label: 'EU-Mitglied', type: 'boolean', required: false },
|
||||
{ key: 'isEEA', label: 'EWR-Mitglied', type: 'boolean', required: false },
|
||||
{ key: 'hasAdequacyDecision', label: 'Angemessenheitsbeschluss', type: 'boolean', required: false },
|
||||
],
|
||||
searchableFields: ['code', 'name', 'riskLevel'],
|
||||
},
|
||||
|
||||
'legal-bases': {
|
||||
id: 'legal-bases',
|
||||
name: 'Rechtsgrundlagen',
|
||||
description: 'DSGVO Art. 6 und Art. 9 Rechtsgrundlagen',
|
||||
module: 'reference',
|
||||
icon: 'Scale',
|
||||
systemCount: LEGAL_BASIS_INFO.length,
|
||||
allowCustom: false,
|
||||
idField: 'type',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'article',
|
||||
fields: [
|
||||
{ key: 'type', label: 'Typ', type: 'text', required: true },
|
||||
{ key: 'article', label: 'Artikel', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'isSpecialCategory', label: 'Besondere Kategorie (Art. 9)', type: 'boolean', required: false },
|
||||
],
|
||||
searchableFields: ['type', 'article', 'name', 'description'],
|
||||
},
|
||||
|
||||
'retention-periods': {
|
||||
id: 'retention-periods',
|
||||
name: 'Aufbewahrungsfristen',
|
||||
description: 'Gesetzliche Standard-Aufbewahrungsfristen',
|
||||
module: 'reference',
|
||||
icon: 'Timer',
|
||||
systemCount: STANDARD_RETENTION_PERIODS.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'legalBasis', 'description'],
|
||||
},
|
||||
|
||||
'eu-legal-frameworks': {
|
||||
id: 'eu-legal-frameworks',
|
||||
name: 'EU-Rechtsrahmen',
|
||||
description: 'EU-weite Datenschutzgesetze und -verordnungen',
|
||||
module: 'reference',
|
||||
icon: 'Landmark',
|
||||
systemCount: EU_BASE_FRAMEWORKS.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'type',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'fullName', label: 'Vollstaendiger Name', type: 'text', required: true },
|
||||
{ key: 'type', label: 'Typ', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'fullName', 'description', 'type'],
|
||||
},
|
||||
|
||||
'national-legal-frameworks': {
|
||||
id: 'national-legal-frameworks',
|
||||
name: 'Nationale Rechtsrahmen',
|
||||
description: 'Nationale Datenschutzgesetze der EU/EWR-Staaten',
|
||||
module: 'reference',
|
||||
icon: 'Flag',
|
||||
systemCount: NATIONAL_FRAMEWORKS.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'countryCode',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'countryCode', label: 'Land', type: 'text', required: true },
|
||||
{ key: 'type', label: 'Typ', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'countryCode', 'description', 'type'],
|
||||
},
|
||||
|
||||
'gdpr-enforcement-cases': {
|
||||
id: 'gdpr-enforcement-cases',
|
||||
name: 'DSGVO-Bussgeldentscheidungen',
|
||||
description: 'Relevante Bussgeldentscheidungen als Referenz',
|
||||
module: 'reference',
|
||||
icon: 'Gavel',
|
||||
systemCount: GDPR_ENFORCEMENT_CASES.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'company',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'country',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'company', label: 'Unternehmen', type: 'text', required: true },
|
||||
{ key: 'country', label: 'Land', type: 'text', required: true },
|
||||
{ key: 'year', label: 'Jahr', type: 'number', required: true },
|
||||
{ key: 'fineOriginal', label: 'Bussgeld (EUR)', type: 'number', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'company', 'country', 'description'],
|
||||
},
|
||||
|
||||
'wp248-criteria': {
|
||||
id: 'wp248-criteria',
|
||||
name: 'WP248 Kriterien',
|
||||
description: 'Kriterien zur DSFA-Pflichtpruefung nach WP248',
|
||||
module: 'dsfa',
|
||||
icon: 'ClipboardCheck',
|
||||
systemCount: WP248_CRITERIA.length,
|
||||
allowCustom: false,
|
||||
idField: 'code',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
fields: [
|
||||
{ key: 'code', label: 'Code', type: 'text', required: true },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'gdprRef', label: 'DSGVO-Referenz', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['code', 'title', 'description', 'gdprRef'],
|
||||
},
|
||||
|
||||
'sdm-goals': {
|
||||
id: 'sdm-goals',
|
||||
name: 'SDM Gewaehrleistungsziele',
|
||||
description: 'Standard-Datenschutzmodell Gewaehrleistungsziele',
|
||||
module: 'dsfa',
|
||||
icon: 'Target',
|
||||
systemCount: SDM_GOALS_ENTRIES.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'article', label: 'DSGVO-Artikel', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'description', 'article'],
|
||||
},
|
||||
|
||||
'dsfa-authority-resources': {
|
||||
id: 'dsfa-authority-resources',
|
||||
name: 'Aufsichtsbehoerden-Ressourcen',
|
||||
description: 'DSFA-Ressourcen der deutschen Aufsichtsbehoerden',
|
||||
module: 'dsfa',
|
||||
icon: 'Building',
|
||||
systemCount: DSFA_AUTHORITY_RESOURCES.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'shortName',
|
||||
descriptionField: 'name',
|
||||
categoryField: 'state',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'shortName', label: 'Kurzname', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Voller Name', type: 'text', required: true },
|
||||
{ key: 'state', label: 'Bundesland', type: 'text', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'shortName', 'name', 'state'],
|
||||
},
|
||||
}
|
||||
|
||||
export const SYSTEM_ENTRIES_MAP: Record<CatalogId, Record<string, unknown>[]> = {
|
||||
'dsfa-risks': RISK_CATALOG as unknown as Record<string, unknown>[],
|
||||
'dsfa-mitigations': MITIGATION_LIBRARY as unknown as Record<string, unknown>[],
|
||||
'ai-risks': AI_RISK_CATALOG as unknown as Record<string, unknown>[],
|
||||
'ai-mitigations': AI_MITIGATION_LIBRARY as unknown as Record<string, unknown>[],
|
||||
'prohibited-ai-practices': PROHIBITED_AI_PRACTICES as unknown as Record<string, unknown>[],
|
||||
'vvt-templates': VVT_BASELINE_CATALOG as unknown as Record<string, unknown>[],
|
||||
'loeschfristen-templates': BASELINE_TEMPLATES as unknown as Record<string, unknown>[],
|
||||
'vendor-templates': VENDOR_TEMPLATES as unknown as Record<string, unknown>[],
|
||||
'country-risk-profiles': COUNTRY_RISK_PROFILES as unknown as Record<string, unknown>[],
|
||||
'legal-bases': LEGAL_BASIS_INFO as unknown as Record<string, unknown>[],
|
||||
'retention-periods': STANDARD_RETENTION_PERIODS as unknown as Record<string, unknown>[],
|
||||
'eu-legal-frameworks': EU_BASE_FRAMEWORKS as unknown as Record<string, unknown>[],
|
||||
'national-legal-frameworks': NATIONAL_FRAMEWORKS as unknown as Record<string, unknown>[],
|
||||
'gdpr-enforcement-cases': GDPR_ENFORCEMENT_CASES as unknown as Record<string, unknown>[],
|
||||
'wp248-criteria': WP248_CRITERIA as unknown as Record<string, unknown>[],
|
||||
'sdm-goals': SDM_GOALS_ENTRIES as unknown as Record<string, unknown>[],
|
||||
'dsfa-authority-resources': DSFA_AUTHORITY_RESOURCES as unknown as Record<string, unknown>[],
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* SDK Catalog Manager - Central Registry
|
||||
*
|
||||
* Maps all SDK catalogs to a unified interface for browsing, searching, and CRUD.
|
||||
*
|
||||
* Public API for browsing, searching, and converting catalog entries.
|
||||
* Registry data definitions live in catalog-registry-data.ts.
|
||||
*/
|
||||
|
||||
import type {
|
||||
@@ -15,22 +16,10 @@ import type {
|
||||
CustomCatalogs,
|
||||
} from './types'
|
||||
|
||||
// =============================================================================
|
||||
// CATALOG DATA IMPORTS
|
||||
// =============================================================================
|
||||
import { CATALOG_REGISTRY, SYSTEM_ENTRIES_MAP } from './catalog-registry-data'
|
||||
|
||||
import { RISK_CATALOG } from '../dsfa/risk-catalog'
|
||||
import { MITIGATION_LIBRARY } from '../dsfa/mitigation-library'
|
||||
import { AI_RISK_CATALOG } from '../dsfa/ai-risk-catalog'
|
||||
import { AI_MITIGATION_LIBRARY } from '../dsfa/ai-mitigation-library'
|
||||
import { PROHIBITED_AI_PRACTICES } from '../dsfa/prohibited-ai-practices'
|
||||
import { EU_BASE_FRAMEWORKS, NATIONAL_FRAMEWORKS } from '../dsfa/eu-legal-frameworks'
|
||||
import { GDPR_ENFORCEMENT_CASES } from '../dsfa/gdpr-enforcement-cases'
|
||||
import { WP248_CRITERIA, SDM_GOALS, DSFA_AUTHORITY_RESOURCES } from '../dsfa/types'
|
||||
import { VVT_BASELINE_CATALOG } from '../vvt-baseline-catalog'
|
||||
import { BASELINE_TEMPLATES } from '../loeschfristen-baseline-catalog'
|
||||
import { VENDOR_TEMPLATES, COUNTRY_RISK_PROFILES } from '../vendor-compliance/catalog/vendor-templates'
|
||||
import { LEGAL_BASIS_INFO, STANDARD_RETENTION_PERIODS } from '../vendor-compliance/catalog/legal-basis'
|
||||
// Re-export so existing imports keep working
|
||||
export { CATALOG_REGISTRY }
|
||||
|
||||
// =============================================================================
|
||||
// HELPER: Resolve localized text fields
|
||||
@@ -46,485 +35,6 @@ function resolveField(value: unknown): string {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SDM_GOALS as entries array (it's a Record, not an array)
|
||||
// =============================================================================
|
||||
|
||||
const SDM_GOALS_ENTRIES = Object.entries(SDM_GOALS).map(([key, val]) => ({
|
||||
id: key,
|
||||
name: val.name,
|
||||
description: val.description,
|
||||
article: val.article,
|
||||
}))
|
||||
|
||||
// =============================================================================
|
||||
// CATALOG REGISTRY
|
||||
// =============================================================================
|
||||
|
||||
export const CATALOG_REGISTRY: Record<CatalogId, CatalogMeta> = {
|
||||
'dsfa-risks': {
|
||||
id: 'dsfa-risks',
|
||||
name: 'DSFA Risikokatalog',
|
||||
description: 'Standardrisiken fuer Datenschutz-Folgenabschaetzungen',
|
||||
module: 'dsfa',
|
||||
icon: 'ShieldAlert',
|
||||
systemCount: RISK_CATALOG.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'category',
|
||||
fields: [
|
||||
{ key: 'id', label: 'Risiko-ID', type: 'text', required: true, placeholder: 'R-XXX-01' },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'category', label: 'Kategorie', type: 'select', required: true, options: [
|
||||
{ value: 'confidentiality', label: 'Vertraulichkeit' },
|
||||
{ value: 'integrity', label: 'Integritaet' },
|
||||
{ value: 'availability', label: 'Verfuegbarkeit' },
|
||||
{ value: 'rights_freedoms', label: 'Rechte & Freiheiten' },
|
||||
]},
|
||||
{ key: 'typicalLikelihood', label: 'Typische Eintrittswahrscheinlichkeit', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
{ key: 'typicalImpact', label: 'Typische Auswirkung', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'category'],
|
||||
},
|
||||
|
||||
'dsfa-mitigations': {
|
||||
id: 'dsfa-mitigations',
|
||||
name: 'DSFA Massnahmenbibliothek',
|
||||
description: 'Technische und organisatorische Massnahmen fuer DSFAs',
|
||||
module: 'dsfa',
|
||||
icon: 'Shield',
|
||||
systemCount: MITIGATION_LIBRARY.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'type',
|
||||
fields: [
|
||||
{ key: 'id', label: 'Massnahmen-ID', type: 'text', required: true, placeholder: 'M-XXX-01' },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'type', label: 'Typ', type: 'select', required: true, options: [
|
||||
{ value: 'technical', label: 'Technisch' },
|
||||
{ value: 'organizational', label: 'Organisatorisch' },
|
||||
{ value: 'legal', label: 'Rechtlich' },
|
||||
]},
|
||||
{ key: 'effectiveness', label: 'Wirksamkeit', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'type', 'legalBasis'],
|
||||
},
|
||||
|
||||
'ai-risks': {
|
||||
id: 'ai-risks',
|
||||
name: 'KI-Risikokatalog',
|
||||
description: 'Spezifische Risiken fuer KI-Systeme',
|
||||
module: 'ai_act',
|
||||
icon: 'Bot',
|
||||
systemCount: AI_RISK_CATALOG.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'category',
|
||||
fields: [
|
||||
{ key: 'id', label: 'Risiko-ID', type: 'text', required: true, placeholder: 'R-AI-XXX-01' },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'category', label: 'Kategorie', type: 'select', required: true, options: [
|
||||
{ value: 'confidentiality', label: 'Vertraulichkeit' },
|
||||
{ value: 'integrity', label: 'Integritaet' },
|
||||
{ value: 'availability', label: 'Verfuegbarkeit' },
|
||||
{ value: 'rights_freedoms', label: 'Rechte & Freiheiten' },
|
||||
]},
|
||||
{ key: 'typicalLikelihood', label: 'Eintrittswahrscheinlichkeit', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
{ key: 'typicalImpact', label: 'Auswirkung', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'category'],
|
||||
},
|
||||
|
||||
'ai-mitigations': {
|
||||
id: 'ai-mitigations',
|
||||
name: 'KI-Massnahmenbibliothek',
|
||||
description: 'Massnahmen fuer KI-spezifische Risiken',
|
||||
module: 'ai_act',
|
||||
icon: 'ShieldCheck',
|
||||
systemCount: AI_MITIGATION_LIBRARY.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'type',
|
||||
fields: [
|
||||
{ key: 'id', label: 'Massnahmen-ID', type: 'text', required: true },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'type', label: 'Typ', type: 'select', required: true, options: [
|
||||
{ value: 'technical', label: 'Technisch' },
|
||||
{ value: 'organizational', label: 'Organisatorisch' },
|
||||
{ value: 'legal', label: 'Rechtlich' },
|
||||
]},
|
||||
{ key: 'effectiveness', label: 'Wirksamkeit', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'type'],
|
||||
},
|
||||
|
||||
'prohibited-ai-practices': {
|
||||
id: 'prohibited-ai-practices',
|
||||
name: 'Verbotene KI-Praktiken',
|
||||
description: 'Absolut und bedingt verbotene KI-Anwendungen nach AI Act',
|
||||
module: 'ai_act',
|
||||
icon: 'Ban',
|
||||
systemCount: PROHIBITED_AI_PRACTICES.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'severity',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'severity', label: 'Schwere', type: 'select', required: true, options: [
|
||||
{ value: 'absolute', label: 'Absolutes Verbot' },
|
||||
{ value: 'conditional', label: 'Bedingtes Verbot' },
|
||||
]},
|
||||
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'severity', 'legalBasis'],
|
||||
},
|
||||
|
||||
'vvt-templates': {
|
||||
id: 'vvt-templates',
|
||||
name: 'VVT Baseline-Vorlagen',
|
||||
description: 'Vorlagen fuer Verarbeitungstaetigkeiten',
|
||||
module: 'vvt',
|
||||
icon: 'FileText',
|
||||
systemCount: VVT_BASELINE_CATALOG.length,
|
||||
allowCustom: true,
|
||||
idField: 'templateId',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'businessFunction',
|
||||
fields: [
|
||||
{ key: 'templateId', label: 'Vorlagen-ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'businessFunction', label: 'Geschaeftsbereich', type: 'select', required: true, options: [
|
||||
{ value: 'hr', label: 'Personal' },
|
||||
{ value: 'finance', label: 'Finanzen' },
|
||||
{ value: 'sales', label: 'Vertrieb' },
|
||||
{ value: 'marketing', label: 'Marketing' },
|
||||
{ value: 'support', label: 'Support' },
|
||||
{ value: 'it', label: 'IT' },
|
||||
{ value: 'other', label: 'Sonstige' },
|
||||
]},
|
||||
{ key: 'protectionLevel', label: 'Schutzniveau', type: 'select', required: false, options: [
|
||||
{ value: 'LOW', label: 'Niedrig' },
|
||||
{ value: 'MEDIUM', label: 'Mittel' },
|
||||
{ value: 'HIGH', label: 'Hoch' },
|
||||
]},
|
||||
],
|
||||
searchableFields: ['templateId', 'name', 'description', 'businessFunction'],
|
||||
},
|
||||
|
||||
'loeschfristen-templates': {
|
||||
id: 'loeschfristen-templates',
|
||||
name: 'Loeschfristen-Vorlagen',
|
||||
description: 'Baseline-Vorlagen fuer Aufbewahrungsfristen',
|
||||
module: 'vvt',
|
||||
icon: 'Clock',
|
||||
systemCount: BASELINE_TEMPLATES.length,
|
||||
allowCustom: true,
|
||||
idField: 'templateId',
|
||||
nameField: 'dataObjectName',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'retentionDriver',
|
||||
fields: [
|
||||
{ key: 'templateId', label: 'Vorlagen-ID', type: 'text', required: true },
|
||||
{ key: 'dataObjectName', label: 'Datenobjekt', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'retentionDuration', label: 'Aufbewahrungsdauer', type: 'number', required: true, min: 0 },
|
||||
{ key: 'retentionUnit', label: 'Einheit', type: 'select', required: true, options: [
|
||||
{ value: 'days', label: 'Tage' },
|
||||
{ value: 'months', label: 'Monate' },
|
||||
{ value: 'years', label: 'Jahre' },
|
||||
]},
|
||||
{ key: 'deletionMethod', label: 'Loeschmethode', type: 'text', required: false },
|
||||
{ key: 'responsibleRole', label: 'Verantwortlich', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['templateId', 'dataObjectName', 'description', 'retentionDriver'],
|
||||
},
|
||||
|
||||
'vendor-templates': {
|
||||
id: 'vendor-templates',
|
||||
name: 'AV-Vorlagen',
|
||||
description: 'Vorlagen fuer Auftragsverarbeitungsvertraege',
|
||||
module: 'vendor',
|
||||
icon: 'Building2',
|
||||
systemCount: VENDOR_TEMPLATES.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'serviceCategory',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'serviceCategory', label: 'Kategorie', type: 'text', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'description', 'serviceCategory'],
|
||||
},
|
||||
|
||||
'country-risk-profiles': {
|
||||
id: 'country-risk-profiles',
|
||||
name: 'Laenderrisikoprofile',
|
||||
description: 'Datenschutz-Risikobewertung nach Laendern',
|
||||
module: 'vendor',
|
||||
icon: 'Globe',
|
||||
systemCount: COUNTRY_RISK_PROFILES.length,
|
||||
allowCustom: false,
|
||||
idField: 'code',
|
||||
nameField: 'name',
|
||||
categoryField: 'riskLevel',
|
||||
fields: [
|
||||
{ key: 'code', label: 'Laendercode', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Land', type: 'text', required: true },
|
||||
{ key: 'riskLevel', label: 'Risikostufe', type: 'select', required: true, options: [
|
||||
{ value: 'LOW', label: 'Niedrig' },
|
||||
{ value: 'MEDIUM', label: 'Mittel' },
|
||||
{ value: 'HIGH', label: 'Hoch' },
|
||||
{ value: 'VERY_HIGH', label: 'Sehr hoch' },
|
||||
]},
|
||||
{ key: 'isEU', label: 'EU-Mitglied', type: 'boolean', required: false },
|
||||
{ key: 'isEEA', label: 'EWR-Mitglied', type: 'boolean', required: false },
|
||||
{ key: 'hasAdequacyDecision', label: 'Angemessenheitsbeschluss', type: 'boolean', required: false },
|
||||
],
|
||||
searchableFields: ['code', 'name', 'riskLevel'],
|
||||
},
|
||||
|
||||
'legal-bases': {
|
||||
id: 'legal-bases',
|
||||
name: 'Rechtsgrundlagen',
|
||||
description: 'DSGVO Art. 6 und Art. 9 Rechtsgrundlagen',
|
||||
module: 'reference',
|
||||
icon: 'Scale',
|
||||
systemCount: LEGAL_BASIS_INFO.length,
|
||||
allowCustom: false,
|
||||
idField: 'type',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'article',
|
||||
fields: [
|
||||
{ key: 'type', label: 'Typ', type: 'text', required: true },
|
||||
{ key: 'article', label: 'Artikel', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'isSpecialCategory', label: 'Besondere Kategorie (Art. 9)', type: 'boolean', required: false },
|
||||
],
|
||||
searchableFields: ['type', 'article', 'name', 'description'],
|
||||
},
|
||||
|
||||
'retention-periods': {
|
||||
id: 'retention-periods',
|
||||
name: 'Aufbewahrungsfristen',
|
||||
description: 'Gesetzliche Standard-Aufbewahrungsfristen',
|
||||
module: 'reference',
|
||||
icon: 'Timer',
|
||||
systemCount: STANDARD_RETENTION_PERIODS.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'legalBasis', 'description'],
|
||||
},
|
||||
|
||||
'eu-legal-frameworks': {
|
||||
id: 'eu-legal-frameworks',
|
||||
name: 'EU-Rechtsrahmen',
|
||||
description: 'EU-weite Datenschutzgesetze und -verordnungen',
|
||||
module: 'reference',
|
||||
icon: 'Landmark',
|
||||
systemCount: EU_BASE_FRAMEWORKS.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'type',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'fullName', label: 'Vollstaendiger Name', type: 'text', required: true },
|
||||
{ key: 'type', label: 'Typ', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'fullName', 'description', 'type'],
|
||||
},
|
||||
|
||||
'national-legal-frameworks': {
|
||||
id: 'national-legal-frameworks',
|
||||
name: 'Nationale Rechtsrahmen',
|
||||
description: 'Nationale Datenschutzgesetze der EU/EWR-Staaten',
|
||||
module: 'reference',
|
||||
icon: 'Flag',
|
||||
systemCount: NATIONAL_FRAMEWORKS.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'countryCode',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'countryCode', label: 'Land', type: 'text', required: true },
|
||||
{ key: 'type', label: 'Typ', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'countryCode', 'description', 'type'],
|
||||
},
|
||||
|
||||
'gdpr-enforcement-cases': {
|
||||
id: 'gdpr-enforcement-cases',
|
||||
name: 'DSGVO-Bussgeldentscheidungen',
|
||||
description: 'Relevante Bussgeldentscheidungen als Referenz',
|
||||
module: 'reference',
|
||||
icon: 'Gavel',
|
||||
systemCount: GDPR_ENFORCEMENT_CASES.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'company',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'country',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'company', label: 'Unternehmen', type: 'text', required: true },
|
||||
{ key: 'country', label: 'Land', type: 'text', required: true },
|
||||
{ key: 'year', label: 'Jahr', type: 'number', required: true },
|
||||
{ key: 'fineOriginal', label: 'Bussgeld (EUR)', type: 'number', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'company', 'country', 'description'],
|
||||
},
|
||||
|
||||
'wp248-criteria': {
|
||||
id: 'wp248-criteria',
|
||||
name: 'WP248 Kriterien',
|
||||
description: 'Kriterien zur DSFA-Pflichtpruefung nach WP248',
|
||||
module: 'dsfa',
|
||||
icon: 'ClipboardCheck',
|
||||
systemCount: WP248_CRITERIA.length,
|
||||
allowCustom: false,
|
||||
idField: 'code',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
fields: [
|
||||
{ key: 'code', label: 'Code', type: 'text', required: true },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'gdprRef', label: 'DSGVO-Referenz', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['code', 'title', 'description', 'gdprRef'],
|
||||
},
|
||||
|
||||
'sdm-goals': {
|
||||
id: 'sdm-goals',
|
||||
name: 'SDM Gewaehrleistungsziele',
|
||||
description: 'Standard-Datenschutzmodell Gewaehrleistungsziele',
|
||||
module: 'dsfa',
|
||||
icon: 'Target',
|
||||
systemCount: SDM_GOALS_ENTRIES.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'article', label: 'DSGVO-Artikel', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'description', 'article'],
|
||||
},
|
||||
|
||||
'dsfa-authority-resources': {
|
||||
id: 'dsfa-authority-resources',
|
||||
name: 'Aufsichtsbehoerden-Ressourcen',
|
||||
description: 'DSFA-Ressourcen der deutschen Aufsichtsbehoerden',
|
||||
module: 'dsfa',
|
||||
icon: 'Building',
|
||||
systemCount: DSFA_AUTHORITY_RESOURCES.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'shortName',
|
||||
descriptionField: 'name',
|
||||
categoryField: 'state',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'shortName', label: 'Kurzname', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Voller Name', type: 'text', required: true },
|
||||
{ key: 'state', label: 'Bundesland', type: 'text', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'shortName', 'name', 'state'],
|
||||
},
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SYSTEM ENTRIES MAP
|
||||
// =============================================================================
|
||||
|
||||
const SYSTEM_ENTRIES_MAP: Record<CatalogId, Record<string, unknown>[]> = {
|
||||
'dsfa-risks': RISK_CATALOG as unknown as Record<string, unknown>[],
|
||||
'dsfa-mitigations': MITIGATION_LIBRARY as unknown as Record<string, unknown>[],
|
||||
'ai-risks': AI_RISK_CATALOG as unknown as Record<string, unknown>[],
|
||||
'ai-mitigations': AI_MITIGATION_LIBRARY as unknown as Record<string, unknown>[],
|
||||
'prohibited-ai-practices': PROHIBITED_AI_PRACTICES as unknown as Record<string, unknown>[],
|
||||
'vvt-templates': VVT_BASELINE_CATALOG as unknown as Record<string, unknown>[],
|
||||
'loeschfristen-templates': BASELINE_TEMPLATES as unknown as Record<string, unknown>[],
|
||||
'vendor-templates': VENDOR_TEMPLATES as unknown as Record<string, unknown>[],
|
||||
'country-risk-profiles': COUNTRY_RISK_PROFILES as unknown as Record<string, unknown>[],
|
||||
'legal-bases': LEGAL_BASIS_INFO as unknown as Record<string, unknown>[],
|
||||
'retention-periods': STANDARD_RETENTION_PERIODS as unknown as Record<string, unknown>[],
|
||||
'eu-legal-frameworks': EU_BASE_FRAMEWORKS as unknown as Record<string, unknown>[],
|
||||
'national-legal-frameworks': NATIONAL_FRAMEWORKS as unknown as Record<string, unknown>[],
|
||||
'gdpr-enforcement-cases': GDPR_ENFORCEMENT_CASES as unknown as Record<string, unknown>[],
|
||||
'wp248-criteria': WP248_CRITERIA as unknown as Record<string, unknown>[],
|
||||
'sdm-goals': SDM_GOALS_ENTRIES as unknown as Record<string, unknown>[],
|
||||
'dsfa-authority-resources': DSFA_AUTHORITY_RESOURCES as unknown as Record<string, unknown>[],
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ENTRY CONVERTERS
|
||||
// =============================================================================
|
||||
|
||||
104
admin-lehrer/lib/sdk/dsfa/types-api.ts
Normal file
104
admin-lehrer/lib/sdk/dsfa/types-api.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* DSFA API Types - Request/response types and UCCA integration
|
||||
*
|
||||
* All types used for API communication with the DSFA backend.
|
||||
*/
|
||||
|
||||
import type { DSFAStatus, DSFARiskLevel } from './types-core'
|
||||
import type { DSFA } from './types-assessment'
|
||||
|
||||
// =============================================================================
|
||||
// API REQUEST/RESPONSE TYPES
|
||||
// =============================================================================
|
||||
|
||||
export interface DSFAListResponse {
|
||||
dsfas: DSFA[]
|
||||
}
|
||||
|
||||
export interface DSFAStatsResponse {
|
||||
status_stats: Record<DSFAStatus | 'total', number>
|
||||
risk_stats: Record<DSFARiskLevel, number>
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface CreateDSFARequest {
|
||||
name: string
|
||||
description?: string
|
||||
processing_description?: string
|
||||
processing_purpose?: string
|
||||
data_categories?: string[]
|
||||
legal_basis?: string
|
||||
}
|
||||
|
||||
export interface CreateDSFAFromAssessmentRequest {
|
||||
name?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export interface CreateDSFAFromAssessmentResponse {
|
||||
dsfa: DSFA
|
||||
prefilled: boolean
|
||||
assessment: unknown // UCCA Assessment
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface UpdateDSFASectionRequest {
|
||||
// Section 1
|
||||
processing_description?: string
|
||||
processing_purpose?: string
|
||||
data_categories?: string[]
|
||||
data_subjects?: string[]
|
||||
recipients?: string[]
|
||||
legal_basis?: string
|
||||
legal_basis_details?: string
|
||||
|
||||
// Section 2
|
||||
necessity_assessment?: string
|
||||
proportionality_assessment?: string
|
||||
data_minimization?: string
|
||||
alternatives_considered?: string
|
||||
retention_justification?: string
|
||||
|
||||
// Section 3
|
||||
overall_risk_level?: DSFARiskLevel
|
||||
risk_score?: number
|
||||
affected_rights?: string[]
|
||||
|
||||
// Section 5
|
||||
dpo_consulted?: boolean
|
||||
dpo_name?: string
|
||||
dpo_opinion?: string
|
||||
authority_consulted?: boolean
|
||||
authority_reference?: string
|
||||
authority_decision?: string
|
||||
}
|
||||
|
||||
export interface SubmitForReviewResponse {
|
||||
message: string
|
||||
dsfa: DSFA
|
||||
}
|
||||
|
||||
export interface ApproveDSFARequest {
|
||||
dpo_opinion: string
|
||||
approved: boolean
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// UCCA INTEGRATION TYPES
|
||||
// =============================================================================
|
||||
|
||||
export interface DSFATriggerInfo {
|
||||
required: boolean
|
||||
reason: string
|
||||
triggered_rules: string[]
|
||||
assessment_id?: string
|
||||
existing_dsfa_id?: string
|
||||
}
|
||||
|
||||
export interface UCCATriggeredRule {
|
||||
code: string
|
||||
title: string
|
||||
description: string
|
||||
severity: 'INFO' | 'WARN' | 'BLOCK'
|
||||
gdpr_ref?: string
|
||||
}
|
||||
246
admin-lehrer/lib/sdk/dsfa/types-assessment.ts
Normal file
246
admin-lehrer/lib/sdk/dsfa/types-assessment.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* DSFA Assessment Types - Sub-types and main DSFA interface
|
||||
*
|
||||
* Risk, mitigation, review, threshold analysis, stakeholder consultation,
|
||||
* and the main DSFA data model.
|
||||
*/
|
||||
|
||||
import type {
|
||||
DSFAStatus,
|
||||
DSFARiskLevel,
|
||||
DSFARiskCategory,
|
||||
DSFAMitigationType,
|
||||
DSFAMitigationStatus,
|
||||
} from './types-core'
|
||||
|
||||
// =============================================================================
|
||||
// SUB-TYPES
|
||||
// =============================================================================
|
||||
|
||||
export interface DSFARisk {
|
||||
id: string
|
||||
category: DSFARiskCategory
|
||||
description: string
|
||||
likelihood: 'low' | 'medium' | 'high'
|
||||
impact: 'low' | 'medium' | 'high'
|
||||
risk_level: string
|
||||
affected_data: string[]
|
||||
}
|
||||
|
||||
export interface DSFAMitigation {
|
||||
id: string
|
||||
risk_id: string
|
||||
description: string
|
||||
type: DSFAMitigationType
|
||||
status: DSFAMitigationStatus
|
||||
implemented_at?: string
|
||||
verified_at?: string
|
||||
residual_risk: 'low' | 'medium' | 'high'
|
||||
tom_reference?: string
|
||||
responsible_party: string
|
||||
}
|
||||
|
||||
export interface DSFAReviewComment {
|
||||
id: string
|
||||
section: number
|
||||
comment: string
|
||||
created_by: string
|
||||
created_at: string
|
||||
resolved: boolean
|
||||
}
|
||||
|
||||
export interface DSFASectionProgress {
|
||||
section_0_complete: boolean // Schwellwertanalyse
|
||||
section_1_complete: boolean // Systematische Beschreibung
|
||||
section_2_complete: boolean // Notwendigkeit & Verhältnismäßigkeit
|
||||
section_3_complete: boolean // Risikobewertung
|
||||
section_4_complete: boolean // Abhilfemaßnahmen
|
||||
section_5_complete: boolean // Betroffenenperspektive (optional)
|
||||
section_6_complete: boolean // DSB & Behördenkonsultation
|
||||
section_7_complete: boolean // Fortschreibung & Review
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SCHWELLWERTANALYSE / VORABPRÜFUNG (Art. 35 Abs. 1 DSGVO)
|
||||
// =============================================================================
|
||||
|
||||
export interface DSFAThresholdAnalysis {
|
||||
id: string
|
||||
dsfa_id?: string
|
||||
performed_at: string
|
||||
performed_by: string
|
||||
|
||||
// WP248 Kriterien-Bewertung
|
||||
criteria_assessment: Array<{
|
||||
criterion_id: string // K1-K9
|
||||
applies: boolean
|
||||
justification: string
|
||||
}>
|
||||
|
||||
// Art. 35 Abs. 3 Prüfung
|
||||
art35_abs3_assessment: Array<{
|
||||
case_id: string // a, b, c
|
||||
applies: boolean
|
||||
justification: string
|
||||
}>
|
||||
|
||||
// Ergebnis
|
||||
dsfa_required: boolean
|
||||
decision_justification: string
|
||||
|
||||
// Dokumentation der Entscheidung (gem. DSK Kurzpapier Nr. 5)
|
||||
documented: boolean
|
||||
documentation_reference?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// BETROFFENENPERSPEKTIVE (Art. 35 Abs. 9 DSGVO)
|
||||
// =============================================================================
|
||||
|
||||
export interface DSFAStakeholderConsultation {
|
||||
id: string
|
||||
stakeholder_type: 'data_subjects' | 'representatives' | 'works_council' | 'other'
|
||||
stakeholder_description: string
|
||||
consultation_date?: string
|
||||
consultation_method: 'survey' | 'interview' | 'workshop' | 'written' | 'other'
|
||||
summary: string
|
||||
concerns_raised: string[]
|
||||
addressed_in_dsfa: boolean
|
||||
response_documentation?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ART. 36 KONSULTATIONSPFLICHT
|
||||
// =============================================================================
|
||||
|
||||
export interface DSFAConsultationRequirement {
|
||||
high_residual_risk: boolean
|
||||
consultation_required: boolean // Art. 36 Abs. 1 DSGVO
|
||||
consultation_reason?: string
|
||||
authority_notified: boolean
|
||||
notification_date?: string
|
||||
authority_response?: string
|
||||
authority_recommendations?: string[]
|
||||
waiting_period_observed: boolean // 8 Wochen gem. Art. 36 Abs. 2
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FORTSCHREIBUNG / REVIEW (Art. 35 Abs. 11 DSGVO)
|
||||
// =============================================================================
|
||||
|
||||
export interface DSFAReviewTrigger {
|
||||
id: string
|
||||
trigger_type: 'scheduled' | 'risk_change' | 'new_technology' | 'new_purpose' | 'incident' | 'regulatory' | 'other'
|
||||
description: string
|
||||
detected_at: string
|
||||
detected_by: string
|
||||
review_required: boolean
|
||||
review_completed: boolean
|
||||
review_date?: string
|
||||
changes_made: string[]
|
||||
}
|
||||
|
||||
export interface DSFAReviewSchedule {
|
||||
next_review_date: string
|
||||
review_frequency_months: number
|
||||
last_review_date?: string
|
||||
review_responsible: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MAIN DSFA TYPE
|
||||
// =============================================================================
|
||||
|
||||
export interface DSFA {
|
||||
id: string
|
||||
tenant_id: string
|
||||
namespace_id?: string
|
||||
processing_activity_id?: string
|
||||
assessment_id?: string
|
||||
name: string
|
||||
description: string
|
||||
|
||||
// Section 0: Schwellwertanalyse / Vorabprüfung (NEU - Art. 35 Abs. 1)
|
||||
threshold_analysis?: DSFAThresholdAnalysis
|
||||
wp248_criteria_met?: string[] // IDs der erfüllten WP248-Kriterien (K1-K9)
|
||||
art35_abs3_triggered?: string[] // IDs der ausgelösten Art. 35 Abs. 3 Fälle
|
||||
|
||||
// Section 1: Systematische Beschreibung (Art. 35 Abs. 7 lit. a)
|
||||
processing_description: string
|
||||
processing_purpose: string
|
||||
data_categories: string[]
|
||||
data_subjects: string[]
|
||||
recipients: string[]
|
||||
legal_basis: string
|
||||
legal_basis_details?: string
|
||||
|
||||
// Section 2: Notwendigkeit & Verhältnismäßigkeit (Art. 35 Abs. 7 lit. b)
|
||||
necessity_assessment: string
|
||||
proportionality_assessment: string
|
||||
data_minimization?: string
|
||||
alternatives_considered?: string
|
||||
retention_justification?: string
|
||||
|
||||
// Section 3: Risikobewertung (Art. 35 Abs. 7 lit. c)
|
||||
risks: DSFARisk[]
|
||||
overall_risk_level: DSFARiskLevel
|
||||
risk_score: number
|
||||
affected_rights?: string[]
|
||||
triggered_rule_codes?: string[]
|
||||
|
||||
// KI-spezifische Trigger (NEU)
|
||||
involves_ai?: boolean
|
||||
ai_trigger_ids?: string[] // IDs der ausgelösten KI-Trigger
|
||||
|
||||
// Section 4: Abhilfemaßnahmen (Art. 35 Abs. 7 lit. d)
|
||||
mitigations: DSFAMitigation[]
|
||||
tom_references?: string[]
|
||||
residual_risk_level?: DSFARiskLevel // Restrisiko nach Maßnahmen
|
||||
|
||||
// Section 5: Stellungnahme DSB (Art. 35 Abs. 2 + Art. 36)
|
||||
dpo_consulted: boolean
|
||||
dpo_consulted_at?: string
|
||||
dpo_name?: string
|
||||
dpo_opinion?: string
|
||||
dpo_approved?: boolean
|
||||
authority_consulted: boolean
|
||||
authority_consulted_at?: string
|
||||
authority_reference?: string
|
||||
authority_decision?: string
|
||||
|
||||
// Art. 36 Konsultationspflicht (NEU)
|
||||
consultation_requirement?: DSFAConsultationRequirement
|
||||
|
||||
// Betroffenenperspektive (NEU - Art. 35 Abs. 9)
|
||||
stakeholder_consultations?: DSFAStakeholderConsultation[]
|
||||
stakeholder_consultation_not_appropriate?: boolean
|
||||
stakeholder_consultation_not_appropriate_reason?: string
|
||||
|
||||
// Workflow & Approval
|
||||
status: DSFAStatus
|
||||
submitted_for_review_at?: string
|
||||
submitted_by?: string
|
||||
conclusion: string
|
||||
review_comments?: DSFAReviewComment[]
|
||||
|
||||
// Section Progress Tracking
|
||||
section_progress: DSFASectionProgress
|
||||
|
||||
// Fortschreibung / Review (NEU - Art. 35 Abs. 11)
|
||||
review_schedule?: DSFAReviewSchedule
|
||||
review_triggers?: DSFAReviewTrigger[]
|
||||
version: number // DSFA-Version für Fortschreibung
|
||||
previous_version_id?: string
|
||||
|
||||
// Referenzen zu behördlichen Ressourcen
|
||||
federal_state?: string // Bundesland für zuständige Aufsichtsbehörde
|
||||
authority_resource_id?: string // ID aus DSFA_AUTHORITY_RESOURCES
|
||||
|
||||
// Metadata & Audit
|
||||
metadata?: Record<string, unknown>
|
||||
created_at: string
|
||||
updated_at: string
|
||||
created_by: string
|
||||
approved_by?: string
|
||||
approved_at?: string
|
||||
}
|
||||
177
admin-lehrer/lib/sdk/dsfa/types-authorities.ts
Normal file
177
admin-lehrer/lib/sdk/dsfa/types-authorities.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* DSFA Authority Types - German data protection authority resources
|
||||
*
|
||||
* DSFA must-lists and resources for all 16 Bundeslaender + BfDI.
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// DSFA MUSS-LISTEN NACH BUNDESLÄNDERN
|
||||
// Quellen: Jeweilige Landesdatenschutzbeauftragte
|
||||
// =============================================================================
|
||||
|
||||
export interface DSFAAuthorityResource {
|
||||
id: string
|
||||
name: string
|
||||
shortName: string
|
||||
state: string // Bundesland oder 'Bund'
|
||||
overviewUrl: string
|
||||
publicSectorListUrl?: string
|
||||
privateSectorListUrl?: string
|
||||
templateUrl?: string
|
||||
additionalResources?: Array<{ title: string; url: string }>
|
||||
}
|
||||
|
||||
export const DSFA_AUTHORITY_RESOURCES: DSFAAuthorityResource[] = [
|
||||
{
|
||||
id: 'bund',
|
||||
name: 'Bundesbeauftragter für den Datenschutz und die Informationsfreiheit',
|
||||
shortName: 'BfDI',
|
||||
state: 'Bund',
|
||||
overviewUrl: 'https://www.bfdi.bund.de/DE/Fachthemen/Inhalte/Technik/Datenschutz-Folgenabschaetzungen.html',
|
||||
publicSectorListUrl: 'https://www.bfdi.bund.de/SharedDocs/Downloads/DE/Muster/Liste_VerarbeitungsvorgaengeArt35.pdf',
|
||||
templateUrl: 'https://www.bfdi.bund.de/SharedDocs/Downloads/DE/Muster/Muster_Hinweise_DSFA.html',
|
||||
},
|
||||
{
|
||||
id: 'bw',
|
||||
name: 'Landesbeauftragter für den Datenschutz und die Informationsfreiheit Baden-Württemberg',
|
||||
shortName: 'LfDI BW',
|
||||
state: 'Baden-Württemberg',
|
||||
overviewUrl: 'https://www.baden-wuerttemberg.datenschutz.de/datenschutz-folgenabschaetzung/',
|
||||
privateSectorListUrl: 'https://www.baden-wuerttemberg.datenschutz.de/wp-content/uploads/2018/05/Liste-von-Verarbeitungsvorg%C3%A4ngen-nach-Art.-35-Abs.-4-DS-GVO-LfDI-BW.pdf',
|
||||
},
|
||||
{
|
||||
id: 'by',
|
||||
name: 'Bayerischer Landesbeauftragter für den Datenschutz',
|
||||
shortName: 'BayLfD',
|
||||
state: 'Bayern',
|
||||
overviewUrl: 'https://www.datenschutz-bayern.de/dsfa/',
|
||||
additionalResources: [
|
||||
{ title: 'DSFA-Module und Formulare', url: 'https://www.datenschutz-bayern.de/dsfa/' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'be',
|
||||
name: 'Berliner Beauftragte für Datenschutz und Informationsfreiheit',
|
||||
shortName: 'BlnBDI',
|
||||
state: 'Berlin',
|
||||
overviewUrl: 'https://www.datenschutz-berlin.de/themen/unternehmen/datenschutz-folgenabschaetzung/',
|
||||
publicSectorListUrl: 'https://www.datenschutz-berlin.de/fileadmin/user_upload/pdf/dokumente/2018-BlnBDI_DSFA-oeffentlich.pdf',
|
||||
privateSectorListUrl: 'https://www.datenschutz-berlin.de/fileadmin/user_upload/pdf/dokumente/2018-BlnBDI_DSFA-nicht-oeffentlich.pdf',
|
||||
},
|
||||
{
|
||||
id: 'bb',
|
||||
name: 'Landesbeauftragte für den Datenschutz und für das Recht auf Akteneinsicht Brandenburg',
|
||||
shortName: 'LDA BB',
|
||||
state: 'Brandenburg',
|
||||
overviewUrl: 'https://www.lda.brandenburg.de/lda/de/datenschutz/datenschutz-folgenabschaetzung/',
|
||||
publicSectorListUrl: 'https://www.lda.brandenburg.de/sixcms/media.php/9/DSFA-Liste_%C3%B6ffentlicher_Bereich.pdf',
|
||||
privateSectorListUrl: 'https://www.lda.brandenburg.de/sixcms/media.php/9/DSFA-Liste_nicht_%C3%B6ffentlicher_Bereich.pdf',
|
||||
},
|
||||
{
|
||||
id: 'hb',
|
||||
name: 'Landesbeauftragte für Datenschutz und Informationsfreiheit Bremen',
|
||||
shortName: 'LfDI HB',
|
||||
state: 'Bremen',
|
||||
overviewUrl: 'https://www.datenschutz.bremen.de/datenschutz/datenschutz-folgenabschaetzung-3884',
|
||||
publicSectorListUrl: 'https://www.datenschutz.bremen.de/sixcms/media.php/13/Liste%20von%20Verarbeitungsvorg%C3%A4ngen%20nach%20Artikel%2035.pdf',
|
||||
privateSectorListUrl: 'https://www.datenschutz.bremen.de/sixcms/media.php/13/DSFA%20Muss-Liste%20LfDI%20HB.pdf',
|
||||
},
|
||||
{
|
||||
id: 'hh',
|
||||
name: 'Hamburgischer Beauftragter für Datenschutz und Informationsfreiheit',
|
||||
shortName: 'HmbBfDI',
|
||||
state: 'Hamburg',
|
||||
overviewUrl: 'https://datenschutz-hamburg.de/datenschutz-folgenabschaetzung',
|
||||
publicSectorListUrl: 'https://datenschutz-hamburg.de/fileadmin/user_upload/HmbBfDI/Datenschutz/Informationen/Liste_Art_35-4_DSGVO_HmbBfDI-oeffentlicher_Bereich_v2.0a.pdf',
|
||||
privateSectorListUrl: 'https://datenschutz-hamburg.de/fileadmin/user_upload/HmbBfDI/Datenschutz/Informationen/DSFA_Muss-Liste_fuer_den_nicht-oeffentlicher_Bereich_-_Stand_17.10.2018.pdf',
|
||||
},
|
||||
{
|
||||
id: 'he',
|
||||
name: 'Hessischer Beauftragter für Datenschutz und Informationsfreiheit',
|
||||
shortName: 'HBDI',
|
||||
state: 'Hessen',
|
||||
overviewUrl: 'https://datenschutz.hessen.de/datenschutz/it-und-datenschutz/datenschutz-folgenabschaetzung',
|
||||
},
|
||||
{
|
||||
id: 'mv',
|
||||
name: 'Landesbeauftragter für Datenschutz und Informationsfreiheit Mecklenburg-Vorpommern',
|
||||
shortName: 'LfDI MV',
|
||||
state: 'Mecklenburg-Vorpommern',
|
||||
overviewUrl: 'https://www.datenschutz-mv.de/datenschutz/DSGVO/Hilfsmittel-zur-Umsetzung/',
|
||||
publicSectorListUrl: 'https://www.datenschutz-mv.de/static/DS/Dateien/DS-GVO/HilfsmittelzurUmsetzung/MV-DSFA-Muss-Liste-Oeffentlicher-Bereich.pdf',
|
||||
},
|
||||
{
|
||||
id: 'ni',
|
||||
name: 'Die Landesbeauftragte für den Datenschutz Niedersachsen',
|
||||
shortName: 'LfD NI',
|
||||
state: 'Niedersachsen',
|
||||
overviewUrl: 'https://www.lfd.niedersachsen.de/dsgvo/liste_von_verarbeitungsvorgangen_nach_art_35_abs_4_ds_gvo/muss-listen-zur-datenschutz-folgenabschatzung-179663.html',
|
||||
publicSectorListUrl: 'https://www.lfd.niedersachsen.de/download/134414/DSFA_Muss-Liste_fuer_den_oeffentlichen_Bereich.pdf',
|
||||
privateSectorListUrl: 'https://www.lfd.niedersachsen.de/download/131098/Liste_von_Verarbeitungsvorgaengen_nach_Art._35_Abs._4_DS-GVO.pdf',
|
||||
},
|
||||
{
|
||||
id: 'nw',
|
||||
name: 'Landesbeauftragte für Datenschutz und Informationsfreiheit Nordrhein-Westfalen',
|
||||
shortName: 'LDI NRW',
|
||||
state: 'Nordrhein-Westfalen',
|
||||
overviewUrl: 'https://www.ldi.nrw.de/datenschutz/wirtschaft/datenschutz-folgenabschaetzung',
|
||||
publicSectorListUrl: 'https://www.ldi.nrw.de/liste-von-verarbeitungsvorgaengen-nach-art-35-abs-4-ds-gvo-fuer-den-oeffentlichen-bereich',
|
||||
},
|
||||
{
|
||||
id: 'rp',
|
||||
name: 'Landesbeauftragter für den Datenschutz und die Informationsfreiheit Rheinland-Pfalz',
|
||||
shortName: 'LfDI RP',
|
||||
state: 'Rheinland-Pfalz',
|
||||
overviewUrl: 'https://www.datenschutz.rlp.de/themen/datenschutz-folgenabschaetzung',
|
||||
},
|
||||
{
|
||||
id: 'sl',
|
||||
name: 'Unabhängiges Datenschutzzentrum Saarland',
|
||||
shortName: 'UDZ SL',
|
||||
state: 'Saarland',
|
||||
overviewUrl: 'https://www.datenschutz.saarland.de/themen/datenschutz-folgenabschaetzung',
|
||||
privateSectorListUrl: 'https://www.datenschutz.saarland.de/fileadmin/user_upload/uds/alle_Dateien_und_Ordner_bis_2025/Download/dsfa_muss_liste_dsk_de.pdf',
|
||||
},
|
||||
{
|
||||
id: 'sn',
|
||||
name: 'Sächsische Datenschutz- und Transparenzbeauftragte',
|
||||
shortName: 'SDTB',
|
||||
state: 'Sachsen',
|
||||
overviewUrl: 'https://www.datenschutz.sachsen.de/datenschutz-folgenabschaetzung.html',
|
||||
additionalResources: [
|
||||
{ title: 'Erforderlichkeit der DSFA', url: 'https://www.datenschutz.sachsen.de/erforderlichkeit.html' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'st',
|
||||
name: 'Landesbeauftragter für den Datenschutz Sachsen-Anhalt',
|
||||
shortName: 'LfD ST',
|
||||
state: 'Sachsen-Anhalt',
|
||||
overviewUrl: 'https://datenschutz.sachsen-anhalt.de/informationen/datenschutz-grundverordnung/liste-datenschutz-folgenabschaetzung',
|
||||
publicSectorListUrl: 'https://datenschutz.sachsen-anhalt.de/fileadmin/Bibliothek/Landesaemter/LfD/Informationen/Internationales/Datenschutz-Grundverordnung/Liste_DSFA/Art-35-Liste-oeffentlicher_Bereich.pdf',
|
||||
privateSectorListUrl: 'https://datenschutz.sachsen-anhalt.de/fileadmin/Bibliothek/Landesaemter/LfD/Informationen/Internationales/Datenschutz-Grundverordnung/Liste_DSFA/Art-35-Liste-nichtoeffentlicher_Bereich.pdf',
|
||||
},
|
||||
{
|
||||
id: 'sh',
|
||||
name: 'Unabhängiges Landeszentrum für Datenschutz Schleswig-Holstein',
|
||||
shortName: 'ULD SH',
|
||||
state: 'Schleswig-Holstein',
|
||||
overviewUrl: 'https://www.datenschutzzentrum.de/datenschutzfolgenabschaetzung/',
|
||||
privateSectorListUrl: 'https://www.datenschutzzentrum.de/uploads/datenschutzfolgenabschaetzung/20180525_LfD-SH_DSFA_Muss-Liste_V1.0.pdf',
|
||||
additionalResources: [
|
||||
{ title: 'Begleittext zur DSFA-Liste', url: 'https://www.datenschutzzentrum.de/uploads/dsgvo/2018_0807_LfD-SH_DSFA_Begleittext_V1.0a.pdf' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'th',
|
||||
name: 'Thüringer Landesbeauftragter für den Datenschutz und die Informationsfreiheit',
|
||||
shortName: 'TLfDI',
|
||||
state: 'Thüringen',
|
||||
overviewUrl: 'https://www.tlfdi.de/datenschutz/datenschutz-folgenabschaetzung/',
|
||||
privateSectorListUrl: 'https://tlfdi.de/fileadmin/tlfdi/datenschutz/dsfa_muss-liste_04_07_18.pdf',
|
||||
additionalResources: [
|
||||
{ title: 'Handreichung DS-FA (nicht-öffentlich)', url: 'https://tlfdi.de/fileadmin/tlfdi/datenschutz/handreichung_ds-fa.pdf' },
|
||||
{ title: 'Handreichung DS-FA (öffentlich)', url: 'https://tlfdi.de/fileadmin/tlfdi/Europa/Handreichung_zur_Datenschutz-Folgenabschaetzung_oeffentlicher_Bereich.pdf' },
|
||||
],
|
||||
},
|
||||
]
|
||||
109
admin-lehrer/lib/sdk/dsfa/types-core.ts
Normal file
109
admin-lehrer/lib/sdk/dsfa/types-core.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* DSFA Core Types - Base enums, type aliases, and constants
|
||||
*
|
||||
* SDM goals, status labels, risk levels, legal bases, affected rights.
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// SDM GEWAEHRLEISTUNGSZIELE (Standard-Datenschutzmodell V2.0)
|
||||
// =============================================================================
|
||||
|
||||
export type SDMGoal =
|
||||
| 'datenminimierung'
|
||||
| 'verfuegbarkeit'
|
||||
| 'integritaet'
|
||||
| 'vertraulichkeit'
|
||||
| 'nichtverkettung'
|
||||
| 'transparenz'
|
||||
| 'intervenierbarkeit'
|
||||
|
||||
export const SDM_GOALS: Record<SDMGoal, { name: string; description: string; article: string }> = {
|
||||
datenminimierung: {
|
||||
name: 'Datenminimierung',
|
||||
description: 'Verarbeitung personenbezogener Daten auf das dem Zweck angemessene, erhebliche und notwendige Mass beschraenken.',
|
||||
article: 'Art. 5 Abs. 1 lit. c DSGVO',
|
||||
},
|
||||
verfuegbarkeit: {
|
||||
name: 'Verfuegbarkeit',
|
||||
description: 'Personenbezogene Daten muessen dem Verantwortlichen zur Verfuegung stehen und ordnungsgemaess im vorgesehenen Prozess verwendet werden koennen.',
|
||||
article: 'Art. 32 Abs. 1 lit. b DSGVO',
|
||||
},
|
||||
integritaet: {
|
||||
name: 'Integritaet',
|
||||
description: 'Personenbezogene Daten bleiben waehrend der Verarbeitung unversehrt, vollstaendig und aktuell.',
|
||||
article: 'Art. 5 Abs. 1 lit. d DSGVO',
|
||||
},
|
||||
vertraulichkeit: {
|
||||
name: 'Vertraulichkeit',
|
||||
description: 'Kein unbefugter Zugriff auf personenbezogene Daten. Nur befugte Personen koennen auf Daten zugreifen.',
|
||||
article: 'Art. 32 Abs. 1 lit. b DSGVO',
|
||||
},
|
||||
nichtverkettung: {
|
||||
name: 'Nichtverkettung',
|
||||
description: 'Personenbezogene Daten duerfen nicht ohne Weiteres fuer einen anderen als den erhobenen Zweck zusammengefuehrt werden (Zweckbindung).',
|
||||
article: 'Art. 5 Abs. 1 lit. b DSGVO',
|
||||
},
|
||||
transparenz: {
|
||||
name: 'Transparenz',
|
||||
description: 'Die Verarbeitung personenbezogener Daten muss fuer Betroffene und Aufsichtsbehoerden nachvollziehbar sein.',
|
||||
article: 'Art. 5 Abs. 1 lit. a DSGVO',
|
||||
},
|
||||
intervenierbarkeit: {
|
||||
name: 'Intervenierbarkeit',
|
||||
description: 'Den Betroffenen werden wirksame Moeglichkeiten der Einflussnahme (Auskunft, Berichtigung, Loeschung, Widerspruch) auf die Verarbeitung gewaehrt.',
|
||||
article: 'Art. 15-21 DSGVO',
|
||||
},
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ENUMS & CONSTANTS
|
||||
// =============================================================================
|
||||
|
||||
export type DSFAStatus = 'draft' | 'in_review' | 'approved' | 'rejected' | 'needs_update'
|
||||
|
||||
export type DSFARiskLevel = 'low' | 'medium' | 'high' | 'very_high'
|
||||
|
||||
export type DSFARiskCategory = 'confidentiality' | 'integrity' | 'availability' | 'rights_freedoms'
|
||||
|
||||
export type DSFAMitigationType = 'technical' | 'organizational' | 'legal'
|
||||
|
||||
export type DSFAMitigationStatus = 'planned' | 'in_progress' | 'implemented' | 'verified'
|
||||
|
||||
export const DSFA_STATUS_LABELS: Record<DSFAStatus, string> = {
|
||||
draft: 'Entwurf',
|
||||
in_review: 'In Prüfung',
|
||||
approved: 'Genehmigt',
|
||||
rejected: 'Abgelehnt',
|
||||
needs_update: 'Überarbeitung erforderlich',
|
||||
}
|
||||
|
||||
export const DSFA_RISK_LEVEL_LABELS: Record<DSFARiskLevel, string> = {
|
||||
low: 'Niedrig',
|
||||
medium: 'Mittel',
|
||||
high: 'Hoch',
|
||||
very_high: 'Sehr Hoch',
|
||||
}
|
||||
|
||||
export const DSFA_LEGAL_BASES = {
|
||||
consent: 'Art. 6 Abs. 1 lit. a DSGVO - Einwilligung',
|
||||
contract: 'Art. 6 Abs. 1 lit. b DSGVO - Vertrag',
|
||||
legal_obligation: 'Art. 6 Abs. 1 lit. c DSGVO - Rechtliche Verpflichtung',
|
||||
vital_interests: 'Art. 6 Abs. 1 lit. d DSGVO - Lebenswichtige Interessen',
|
||||
public_interest: 'Art. 6 Abs. 1 lit. e DSGVO - Öffentliches Interesse',
|
||||
legitimate_interest: 'Art. 6 Abs. 1 lit. f DSGVO - Berechtigtes Interesse',
|
||||
}
|
||||
|
||||
export const DSFA_AFFECTED_RIGHTS = [
|
||||
{ id: 'right_to_information', label: 'Recht auf Information (Art. 13/14)' },
|
||||
{ id: 'right_of_access', label: 'Auskunftsrecht (Art. 15)' },
|
||||
{ id: 'right_to_rectification', label: 'Recht auf Berichtigung (Art. 16)' },
|
||||
{ id: 'right_to_erasure', label: 'Recht auf Löschung (Art. 17)' },
|
||||
{ id: 'right_to_restriction', label: 'Recht auf Einschränkung (Art. 18)' },
|
||||
{ id: 'right_to_data_portability', label: 'Recht auf Datenübertragbarkeit (Art. 20)' },
|
||||
{ id: 'right_to_object', label: 'Widerspruchsrecht (Art. 21)' },
|
||||
{ id: 'right_not_to_be_profiled', label: 'Recht bzgl. Profiling (Art. 22)' },
|
||||
{ id: 'freedom_of_expression', label: 'Meinungsfreiheit' },
|
||||
{ id: 'freedom_of_association', label: 'Versammlungsfreiheit' },
|
||||
{ id: 'non_discrimination', label: 'Nichtdiskriminierung' },
|
||||
{ id: 'data_security', label: 'Datensicherheit' },
|
||||
]
|
||||
202
admin-lehrer/lib/sdk/dsfa/types-risk-helpers.ts
Normal file
202
admin-lehrer/lib/sdk/dsfa/types-risk-helpers.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* DSFA Risk Helpers - Risk matrix, calculation functions, and utility helpers
|
||||
*
|
||||
* Risk matrix cells, risk level calculation, WP248 checks,
|
||||
* Art. 36 consultation, authority lookups, review helpers, AI triggers.
|
||||
*/
|
||||
|
||||
import type { DSFARiskLevel } from './types-core'
|
||||
import type { DSFAConsultationRequirement, DSFAReviewTrigger, DSFAReviewSchedule } from './types-assessment'
|
||||
import { DSFA_AUTHORITY_RESOURCES } from './types-authorities'
|
||||
import type { DSFAAuthorityResource } from './types-authorities'
|
||||
import { WP248_CRITERIA, ART35_ABS3_CASES, AI_DSFA_TRIGGERS } from './types-wp248'
|
||||
|
||||
// =============================================================================
|
||||
// RISK MATRIX HELPERS
|
||||
// =============================================================================
|
||||
|
||||
export interface RiskMatrixCell {
|
||||
likelihood: 'low' | 'medium' | 'high'
|
||||
impact: 'low' | 'medium' | 'high'
|
||||
level: DSFARiskLevel
|
||||
score: number
|
||||
}
|
||||
|
||||
export const RISK_MATRIX: RiskMatrixCell[] = [
|
||||
// Low likelihood
|
||||
{ likelihood: 'low', impact: 'low', level: 'low', score: 10 },
|
||||
{ likelihood: 'low', impact: 'medium', level: 'low', score: 20 },
|
||||
{ likelihood: 'low', impact: 'high', level: 'medium', score: 40 },
|
||||
// Medium likelihood
|
||||
{ likelihood: 'medium', impact: 'low', level: 'low', score: 20 },
|
||||
{ likelihood: 'medium', impact: 'medium', level: 'medium', score: 50 },
|
||||
{ likelihood: 'medium', impact: 'high', level: 'high', score: 70 },
|
||||
// High likelihood
|
||||
{ likelihood: 'high', impact: 'low', level: 'medium', score: 40 },
|
||||
{ likelihood: 'high', impact: 'medium', level: 'high', score: 70 },
|
||||
{ likelihood: 'high', impact: 'high', level: 'very_high', score: 90 },
|
||||
]
|
||||
|
||||
export function calculateRiskLevel(
|
||||
likelihood: 'low' | 'medium' | 'high',
|
||||
impact: 'low' | 'medium' | 'high'
|
||||
): { level: DSFARiskLevel; score: number } {
|
||||
const cell = RISK_MATRIX.find(c => c.likelihood === likelihood && c.impact === impact)
|
||||
return cell ? { level: cell.level, score: cell.score } : { level: 'medium', score: 50 }
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Prüft anhand der WP248-Kriterien, ob eine DSFA erforderlich ist.
|
||||
* Regel: Bei >= 2 erfüllten Kriterien ist DSFA in den meisten Fällen erforderlich.
|
||||
* @param criteriaIds Array der erfüllten Kriterien-IDs (z.B. ['K1', 'K4'])
|
||||
* @returns Objekt mit Ergebnis und Begründung
|
||||
*/
|
||||
export function checkDSFARequiredByWP248(criteriaIds: string[]): {
|
||||
required: boolean
|
||||
confidence: 'definite' | 'likely' | 'possible' | 'unlikely'
|
||||
reason: string
|
||||
} {
|
||||
const count = criteriaIds.length
|
||||
|
||||
if (count >= 2) {
|
||||
return {
|
||||
required: true,
|
||||
confidence: 'definite',
|
||||
reason: `${count} WP248-Kriterien erfüllt (>= 2). DSFA ist in den meisten Fällen erforderlich.`,
|
||||
}
|
||||
}
|
||||
|
||||
if (count === 1) {
|
||||
return {
|
||||
required: false,
|
||||
confidence: 'possible',
|
||||
reason: '1 WP248-Kriterium erfüllt. DSFA kann je nach Risiko dennoch erforderlich sein. Einzelfallprüfung empfohlen.',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
required: false,
|
||||
confidence: 'unlikely',
|
||||
reason: 'Keine WP248-Kriterien erfüllt. DSFA wahrscheinlich nicht erforderlich, sofern kein Art. 35 Abs. 3 Fall vorliegt.',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob eine Konsultation der Aufsichtsbehörde gem. Art. 36 DSGVO erforderlich ist.
|
||||
* Erforderlich wenn: Hohes Restrisiko trotz geplanter Maßnahmen.
|
||||
*/
|
||||
export function checkArt36ConsultationRequired(
|
||||
residualRiskLevel: DSFARiskLevel,
|
||||
mitigationsImplemented: boolean
|
||||
): DSFAConsultationRequirement {
|
||||
const highResidual = residualRiskLevel === 'high' || residualRiskLevel === 'very_high'
|
||||
const consultationRequired = highResidual && mitigationsImplemented
|
||||
|
||||
return {
|
||||
high_residual_risk: highResidual,
|
||||
consultation_required: consultationRequired,
|
||||
consultation_reason: consultationRequired
|
||||
? 'Trotz geplanter Maßnahmen verbleibt ein hohes Restrisiko. Gem. Art. 36 Abs. 1 DSGVO ist vor der Verarbeitung die Aufsichtsbehörde zu konsultieren.'
|
||||
: highResidual
|
||||
? 'Hohes Restrisiko festgestellt, aber Maßnahmen noch nicht vollständig umgesetzt.'
|
||||
: undefined,
|
||||
authority_notified: false,
|
||||
waiting_period_observed: false,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die zuständige Aufsichtsbehörde für ein Bundesland zurück.
|
||||
*/
|
||||
export function getAuthorityResource(stateId: string): DSFAAuthorityResource | undefined {
|
||||
return DSFA_AUTHORITY_RESOURCES.find(r => r.id === stateId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle Bundesländer als Auswahlliste zurück.
|
||||
*/
|
||||
export function getFederalStateOptions(): Array<{ value: string; label: string }> {
|
||||
return DSFA_AUTHORITY_RESOURCES.map(r => ({
|
||||
value: r.id,
|
||||
label: r.state,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob ein Review-Trigger eine Aktualisierung der DSFA erfordert.
|
||||
*/
|
||||
export function checkReviewRequired(triggers: DSFAReviewTrigger[]): {
|
||||
required: boolean
|
||||
pendingTriggers: DSFAReviewTrigger[]
|
||||
} {
|
||||
const pendingTriggers = triggers.filter(t => t.review_required && !t.review_completed)
|
||||
return {
|
||||
required: pendingTriggers.length > 0,
|
||||
pendingTriggers,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet das nächste Review-Datum basierend auf dem Schedule.
|
||||
*/
|
||||
export function calculateNextReviewDate(schedule: DSFAReviewSchedule): Date {
|
||||
const lastReview = schedule.last_review_date
|
||||
? new Date(schedule.last_review_date)
|
||||
: new Date()
|
||||
|
||||
const nextReview = new Date(lastReview)
|
||||
nextReview.setMonth(nextReview.getMonth() + schedule.review_frequency_months)
|
||||
return nextReview
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob KI-spezifische DSFA-Trigger erfüllt sind.
|
||||
*/
|
||||
export function checkAIDSFATriggers(
|
||||
aiTriggerIds: string[]
|
||||
): { triggered: boolean; triggers: typeof AI_DSFA_TRIGGERS } {
|
||||
const triggered = AI_DSFA_TRIGGERS.filter(t => aiTriggerIds.includes(t.id))
|
||||
return {
|
||||
triggered: triggered.length > 0,
|
||||
triggers: triggered,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert eine Checkliste für die Schwellwertanalyse.
|
||||
*/
|
||||
export function generateThresholdAnalysisChecklist(): Array<{
|
||||
category: string
|
||||
items: Array<{ id: string; label: string; description: string }>
|
||||
}> {
|
||||
return [
|
||||
{
|
||||
category: 'WP248 Kriterien (Art.-29-Datenschutzgruppe)',
|
||||
items: WP248_CRITERIA.map(c => ({
|
||||
id: c.id,
|
||||
label: `${c.code}: ${c.title}`,
|
||||
description: c.description,
|
||||
})),
|
||||
},
|
||||
{
|
||||
category: 'Art. 35 Abs. 3 DSGVO Regelbeispiele',
|
||||
items: ART35_ABS3_CASES.map(c => ({
|
||||
id: c.id,
|
||||
label: `lit. ${c.lit}: ${c.title}`,
|
||||
description: c.description,
|
||||
})),
|
||||
},
|
||||
{
|
||||
category: 'KI-spezifische Trigger (Deutsche DSFA-Liste)',
|
||||
items: AI_DSFA_TRIGGERS.map(t => ({
|
||||
id: t.id,
|
||||
label: t.title,
|
||||
description: t.description,
|
||||
})),
|
||||
},
|
||||
]
|
||||
}
|
||||
95
admin-lehrer/lib/sdk/dsfa/types-ui.ts
Normal file
95
admin-lehrer/lib/sdk/dsfa/types-ui.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* DSFA UI Types - Section configuration for the DSFA wizard UI
|
||||
*
|
||||
* Defines the section structure, labels, and GDPR references
|
||||
* used by the frontend wizard components.
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// HELPER TYPES FOR UI
|
||||
// =============================================================================
|
||||
|
||||
export interface DSFASectionConfig {
|
||||
number: number
|
||||
title: string
|
||||
titleDE: string
|
||||
description: string
|
||||
gdprRef: string
|
||||
fields: string[]
|
||||
required: boolean
|
||||
}
|
||||
|
||||
export const DSFA_SECTIONS: DSFASectionConfig[] = [
|
||||
{
|
||||
number: 0,
|
||||
title: 'Threshold Analysis',
|
||||
titleDE: 'Schwellwertanalyse',
|
||||
description: 'Prüfen Sie anhand der WP248-Kriterien und Art. 35 Abs. 3, ob eine DSFA erforderlich ist. Die Entscheidung ist zu dokumentieren.',
|
||||
gdprRef: 'Art. 35 Abs. 1 DSGVO, WP248 rev.01',
|
||||
fields: ['threshold_analysis', 'wp248_criteria_met', 'art35_abs3_triggered'],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
number: 1,
|
||||
title: 'Processing Description',
|
||||
titleDE: 'Systematische Beschreibung',
|
||||
description: 'Beschreiben Sie die geplante Verarbeitung, ihren Zweck, die Datenkategorien und Rechtsgrundlage.',
|
||||
gdprRef: 'Art. 35 Abs. 7 lit. a DSGVO',
|
||||
fields: ['processing_description', 'processing_purpose', 'data_categories', 'data_subjects', 'recipients', 'legal_basis'],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
number: 2,
|
||||
title: 'Necessity & Proportionality',
|
||||
titleDE: 'Notwendigkeit & Verhältnismäßigkeit',
|
||||
description: 'Begründen Sie, warum die Verarbeitung notwendig ist und welche Alternativen geprüft wurden.',
|
||||
gdprRef: 'Art. 35 Abs. 7 lit. b DSGVO',
|
||||
fields: ['necessity_assessment', 'proportionality_assessment', 'data_minimization', 'alternatives_considered'],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
number: 3,
|
||||
title: 'Risk Assessment',
|
||||
titleDE: 'Risikobewertung',
|
||||
description: 'Identifizieren und bewerten Sie die Risiken für die Rechte und Freiheiten der Betroffenen.',
|
||||
gdprRef: 'Art. 35 Abs. 7 lit. c DSGVO',
|
||||
fields: ['risks', 'overall_risk_level', 'risk_score', 'affected_rights', 'involves_ai', 'ai_trigger_ids'],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
number: 4,
|
||||
title: 'Mitigation Measures',
|
||||
titleDE: 'Abhilfemaßnahmen',
|
||||
description: 'Definieren Sie technische und organisatorische Maßnahmen zur Risikominimierung und bewerten Sie das Restrisiko.',
|
||||
gdprRef: 'Art. 35 Abs. 7 lit. d DSGVO',
|
||||
fields: ['mitigations', 'tom_references', 'residual_risk_level'],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
number: 5,
|
||||
title: 'Stakeholder Consultation',
|
||||
titleDE: 'Betroffenenperspektive',
|
||||
description: 'Dokumentieren Sie, ob und wie die Standpunkte der Betroffenen eingeholt wurden (z.B. Betriebsrat, Nutzerumfragen).',
|
||||
gdprRef: 'Art. 35 Abs. 9 DSGVO',
|
||||
fields: ['stakeholder_consultations', 'stakeholder_consultation_not_appropriate', 'stakeholder_consultation_not_appropriate_reason'],
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
number: 6,
|
||||
title: 'DPO Opinion & Authority Consultation',
|
||||
titleDE: 'DSB-Stellungnahme & Behördenkonsultation',
|
||||
description: 'Dokumentieren Sie die Konsultation des DSB und prüfen Sie, ob bei hohem Restrisiko eine Behördenkonsultation erforderlich ist.',
|
||||
gdprRef: 'Art. 35 Abs. 2, Art. 36 DSGVO',
|
||||
fields: ['dpo_consulted', 'dpo_opinion', 'consultation_requirement', 'authority_consulted', 'authority_reference'],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
number: 7,
|
||||
title: 'Review & Maintenance',
|
||||
titleDE: 'Fortschreibung & Review',
|
||||
description: 'Planen Sie regelmäßige Überprüfungen und dokumentieren Sie Änderungen, die eine Aktualisierung der DSFA erfordern.',
|
||||
gdprRef: 'Art. 35 Abs. 11 DSGVO',
|
||||
fields: ['review_schedule', 'review_triggers', 'version'],
|
||||
required: true,
|
||||
},
|
||||
]
|
||||
183
admin-lehrer/lib/sdk/dsfa/types-wp248.ts
Normal file
183
admin-lehrer/lib/sdk/dsfa/types-wp248.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* DSFA WP248 Types - Threshold analysis criteria, Art. 35 cases, AI triggers
|
||||
*
|
||||
* WP248 rev.01 criteria, DSK Kurzpapier Nr. 5, Art. 35 Abs. 3 DSGVO cases,
|
||||
* and AI-specific DSFA triggers.
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// WP248 REV.01 KRITERIEN (Schwellwertanalyse)
|
||||
// Quelle: Artikel-29-Datenschutzgruppe, bestätigt durch EDSA
|
||||
// =============================================================================
|
||||
|
||||
export interface WP248Criterion {
|
||||
id: string
|
||||
code: string
|
||||
title: string
|
||||
description: string
|
||||
examples: string[]
|
||||
gdprRef?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* WP248 rev.01 Kriterien zur Bestimmung der DSFA-Pflicht
|
||||
* Regel: Bei >= 2 erfüllten Kriterien ist DSFA in den meisten Fällen erforderlich
|
||||
*/
|
||||
export const WP248_CRITERIA: WP248Criterion[] = [
|
||||
{
|
||||
id: 'scoring_profiling',
|
||||
code: 'K1',
|
||||
title: 'Bewertung oder Scoring',
|
||||
description: 'Einschließlich Profiling und Prognosen, insbesondere zu Arbeitsleistung, wirtschaftlicher Lage, Gesundheit, persönlichen Vorlieben, Zuverlässigkeit, Verhalten, Aufenthaltsort oder Ortswechsel.',
|
||||
examples: ['Bonitätsprüfung', 'Leistungsbeurteilung', 'Verhaltensanalyse'],
|
||||
gdprRef: 'Art. 35 Abs. 3 lit. a DSGVO',
|
||||
},
|
||||
{
|
||||
id: 'automated_decision',
|
||||
code: 'K2',
|
||||
title: 'Automatisierte Entscheidungsfindung mit Rechtswirkung',
|
||||
description: 'Automatisierte Verarbeitung, die als Grundlage für Entscheidungen dient, die Rechtswirkung gegenüber natürlichen Personen entfalten oder diese erheblich beeinträchtigen.',
|
||||
examples: ['Automatische Kreditvergabe', 'Automatische Bewerbungsablehnung', 'Algorithmenbasierte Preisgestaltung'],
|
||||
gdprRef: 'Art. 22 DSGVO',
|
||||
},
|
||||
{
|
||||
id: 'systematic_monitoring',
|
||||
code: 'K3',
|
||||
title: 'Systematische Überwachung',
|
||||
description: 'Verarbeitung zur Beobachtung, Überwachung oder Kontrolle von betroffenen Personen, einschließlich Datenerhebung über Netzwerke oder systematische Überwachung öffentlicher Bereiche.',
|
||||
examples: ['Videoüberwachung', 'WLAN-Tracking', 'GPS-Ortung', 'Mitarbeiterüberwachung'],
|
||||
gdprRef: 'Art. 35 Abs. 3 lit. c DSGVO',
|
||||
},
|
||||
{
|
||||
id: 'sensitive_data',
|
||||
code: 'K4',
|
||||
title: 'Sensible Daten oder höchst persönliche Daten',
|
||||
description: 'Verarbeitung besonderer Kategorien personenbezogener Daten (Art. 9), strafrechtlicher Daten (Art. 10) oder anderer höchst persönlicher Daten wie Kommunikationsinhalte, Standortdaten, Finanzinformationen.',
|
||||
examples: ['Gesundheitsdaten', 'Biometrische Daten', 'Genetische Daten', 'Politische Meinungen', 'Gewerkschaftszugehörigkeit'],
|
||||
gdprRef: 'Art. 9, Art. 10 DSGVO',
|
||||
},
|
||||
{
|
||||
id: 'large_scale',
|
||||
code: 'K5',
|
||||
title: 'Datenverarbeitung in großem Umfang',
|
||||
description: 'Berücksichtigt werden: Zahl der Betroffenen, Datenmenge, Dauer der Verarbeitung, geografische Reichweite.',
|
||||
examples: ['Landesweite Datenbanken', 'Millionen von Nutzern', 'Mehrjährige Speicherung'],
|
||||
gdprRef: 'Erwägungsgrund 91 DSGVO',
|
||||
},
|
||||
{
|
||||
id: 'matching_combining',
|
||||
code: 'K6',
|
||||
title: 'Abgleichen oder Zusammenführen von Datensätzen',
|
||||
description: 'Datensätze aus verschiedenen Quellen, die für unterschiedliche Zwecke und/oder von verschiedenen Verantwortlichen erhoben wurden, werden abgeglichen oder zusammengeführt.',
|
||||
examples: ['Data Warehousing', 'Big Data Analytics', 'Zusammenführung von Online-/Offline-Daten'],
|
||||
},
|
||||
{
|
||||
id: 'vulnerable_subjects',
|
||||
code: 'K7',
|
||||
title: 'Daten zu schutzbedürftigen Betroffenen',
|
||||
description: 'Verarbeitung von Daten schutzbedürftiger Personen, bei denen ein Ungleichgewicht zwischen Betroffenem und Verantwortlichem besteht.',
|
||||
examples: ['Kinder/Minderjährige', 'Arbeitnehmer', 'Patienten', 'Ältere Menschen', 'Asylbewerber'],
|
||||
gdprRef: 'Erwägungsgrund 75 DSGVO',
|
||||
},
|
||||
{
|
||||
id: 'innovative_technology',
|
||||
code: 'K8',
|
||||
title: 'Innovative Nutzung oder Anwendung neuer technologischer oder organisatorischer Lösungen',
|
||||
description: 'Einsatz neuer Technologien kann neue Formen der Datenerhebung und -nutzung mit sich bringen, möglicherweise mit hohem Risiko für Rechte und Freiheiten.',
|
||||
examples: ['Künstliche Intelligenz', 'Machine Learning', 'IoT-Geräte', 'Biometrische Erkennung', 'Blockchain'],
|
||||
gdprRef: 'Erwägungsgrund 89, 91 DSGVO',
|
||||
},
|
||||
{
|
||||
id: 'preventing_rights',
|
||||
code: 'K9',
|
||||
title: 'Verarbeitung, die Betroffene an der Ausübung eines Rechts oder der Nutzung einer Dienstleistung hindert',
|
||||
description: 'Verarbeitungsvorgänge, die darauf abzielen, einer Person den Zugang zu einer Dienstleistung oder den Abschluss eines Vertrags zu ermöglichen oder zu verweigern.',
|
||||
examples: ['Zugang zu Sozialleistungen', 'Kreditvergabe', 'Versicherungsabschluss'],
|
||||
gdprRef: 'Art. 22 DSGVO',
|
||||
},
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// DSK KURZPAPIER NR. 5 REFERENZEN
|
||||
// =============================================================================
|
||||
|
||||
export const DSK_KURZPAPIER_5 = {
|
||||
title: 'Kurzpapier Nr. 5: Datenschutz-Folgenabschätzung nach Art. 35 DS-GVO',
|
||||
source: 'Datenschutzkonferenz (DSK)',
|
||||
url: 'https://www.datenschutzkonferenz-online.de/media/kp/dsk_kpnr_5.pdf',
|
||||
license: 'Datenlizenz Deutschland – Namensnennung – Version 2.0 (DL-DE BY 2.0)',
|
||||
licenseUrl: 'https://www.govdata.de/dl-de/by-2-0',
|
||||
processSteps: [
|
||||
{ step: 1, title: 'Projektteam bilden', description: 'Interdisziplinäres Team aus Datenschutz, Fachprozess, IT/Sicherheit' },
|
||||
{ step: 2, title: 'Verarbeitung abgrenzen', description: 'Scope definieren, Datenflüsse und Zwecke beschreiben' },
|
||||
{ step: 3, title: 'Prüfung der Notwendigkeit', description: 'Alternativen prüfen, Datenminimierung bewerten' },
|
||||
{ step: 4, title: 'Risiken identifizieren', description: 'Risikoquellen ermitteln, Schäden bewerten' },
|
||||
{ step: 5, title: 'Maßnahmen festlegen', description: 'TOM definieren, Restrisiko bewerten' },
|
||||
{ step: 6, title: 'Bericht erstellen', description: 'DSFA-Bericht dokumentieren, ggf. veröffentlichen' },
|
||||
{ step: 7, title: 'Fortschreibung', description: 'DSFA bei Änderungen aktualisieren' },
|
||||
],
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ART. 35 ABS. 3 DSGVO - REGELBEISPIELE
|
||||
// =============================================================================
|
||||
|
||||
export const ART35_ABS3_CASES = [
|
||||
{
|
||||
id: 'profiling_legal_effects',
|
||||
lit: 'a',
|
||||
title: 'Profiling mit Rechtswirkung',
|
||||
description: 'Systematische und umfassende Bewertung persönlicher Aspekte natürlicher Personen, die sich auf automatisierte Verarbeitung einschließlich Profiling gründet und die ihrerseits als Grundlage für Entscheidungen dient, die Rechtswirkung gegenüber natürlichen Personen entfalten oder diese in ähnlich erheblicher Weise beeinträchtigen.',
|
||||
gdprRef: 'Art. 35 Abs. 3 lit. a DSGVO',
|
||||
},
|
||||
{
|
||||
id: 'special_categories',
|
||||
lit: 'b',
|
||||
title: 'Besondere Datenkategorien in großem Umfang',
|
||||
description: 'Umfangreiche Verarbeitung besonderer Kategorien von personenbezogenen Daten gemäß Artikel 9 Absatz 1 oder von personenbezogenen Daten über strafrechtliche Verurteilungen und Straftaten gemäß Artikel 10.',
|
||||
gdprRef: 'Art. 35 Abs. 3 lit. b DSGVO',
|
||||
},
|
||||
{
|
||||
id: 'public_monitoring',
|
||||
lit: 'c',
|
||||
title: 'Systematische Überwachung öffentlicher Bereiche',
|
||||
description: 'Systematische umfangreiche Überwachung öffentlich zugänglicher Bereiche.',
|
||||
gdprRef: 'Art. 35 Abs. 3 lit. c DSGVO',
|
||||
},
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// KI-SPEZIFISCHE DSFA-TRIGGER
|
||||
// Quelle: Deutsche DSFA-Liste (nicht-öffentlicher Bereich)
|
||||
// =============================================================================
|
||||
|
||||
export const AI_DSFA_TRIGGERS = [
|
||||
{
|
||||
id: 'ai_interaction',
|
||||
title: 'KI zur Steuerung der Interaktion mit Betroffenen',
|
||||
description: 'Einsatz von künstlicher Intelligenz zur Steuerung der Interaktion mit betroffenen Personen.',
|
||||
examples: ['KI-gestützter Kundensupport', 'Chatbots mit personenbezogener Verarbeitung', 'Automatisierte Kommunikation'],
|
||||
requiresDSFA: true,
|
||||
},
|
||||
{
|
||||
id: 'ai_personal_aspects',
|
||||
title: 'KI zur Bewertung persönlicher Aspekte',
|
||||
description: 'Einsatz von künstlicher Intelligenz zur Bewertung persönlicher Aspekte natürlicher Personen.',
|
||||
examples: ['Automatisierte Stimmungsanalyse', 'Verhaltensvorhersagen', 'Persönlichkeitsprofile'],
|
||||
requiresDSFA: true,
|
||||
},
|
||||
{
|
||||
id: 'ai_decision_making',
|
||||
title: 'KI-basierte automatisierte Entscheidungen',
|
||||
description: 'Automatisierte Entscheidungsfindung auf Basis von KI mit erheblicher Auswirkung auf Betroffene.',
|
||||
examples: ['Automatische Kreditvergabe', 'KI-basiertes Recruiting', 'Algorithmenbasierte Preisgestaltung'],
|
||||
requiresDSFA: true,
|
||||
},
|
||||
{
|
||||
id: 'ai_training_personal_data',
|
||||
title: 'KI-Training mit personenbezogenen Daten',
|
||||
description: 'Training von KI-Modellen mit personenbezogenen Daten, insbesondere sensiblen Daten.',
|
||||
examples: ['Training mit Gesundheitsdaten', 'Fine-Tuning mit Kundendaten', 'ML mit biometrischen Daten'],
|
||||
requiresDSFA: true,
|
||||
},
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
99
admin-lehrer/lib/sdk/export-pdf-helpers.ts
Normal file
99
admin-lehrer/lib/sdk/export-pdf-helpers.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* PDF Export Helper Functions
|
||||
* Shared formatting utilities for jsPDF document generation
|
||||
*/
|
||||
|
||||
import jsPDF from 'jspdf'
|
||||
import { SDKState } from './types'
|
||||
import { LABELS_DE } from './export-types'
|
||||
|
||||
// =============================================================================
|
||||
// FORMATTING HELPERS
|
||||
// =============================================================================
|
||||
|
||||
export function formatDate(date: Date | string | undefined): string {
|
||||
if (!date) return '-'
|
||||
const d = typeof date === 'string' ? new Date(date) : date
|
||||
return d.toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PAGE LAYOUT HELPERS
|
||||
// =============================================================================
|
||||
|
||||
export function addHeader(doc: jsPDF, title: string, pageNum: number, totalPages: number): void {
|
||||
const pageWidth = doc.internal.pageSize.getWidth()
|
||||
|
||||
// Header line
|
||||
doc.setDrawColor(147, 51, 234) // Purple
|
||||
doc.setLineWidth(0.5)
|
||||
doc.line(20, 15, pageWidth - 20, 15)
|
||||
|
||||
// Title
|
||||
doc.setFontSize(10)
|
||||
doc.setTextColor(100)
|
||||
doc.text(title, 20, 12)
|
||||
|
||||
// Page number
|
||||
doc.text(`${LABELS_DE.page} ${pageNum}/${totalPages}`, pageWidth - 40, 12)
|
||||
}
|
||||
|
||||
export function addFooter(doc: jsPDF, state: SDKState): void {
|
||||
const pageWidth = doc.internal.pageSize.getWidth()
|
||||
const pageHeight = doc.internal.pageSize.getHeight()
|
||||
|
||||
// Footer line
|
||||
doc.setDrawColor(200)
|
||||
doc.setLineWidth(0.3)
|
||||
doc.line(20, pageHeight - 15, pageWidth - 20, pageHeight - 15)
|
||||
|
||||
// Footer text
|
||||
doc.setFontSize(8)
|
||||
doc.setTextColor(150)
|
||||
doc.text(`Tenant: ${state.tenantId} | ${LABELS_DE.generatedAt}: ${formatDate(new Date())}`, 20, pageHeight - 10)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONTENT HELPERS
|
||||
// =============================================================================
|
||||
|
||||
export function addSectionTitle(doc: jsPDF, title: string, y: number): number {
|
||||
doc.setFontSize(14)
|
||||
doc.setTextColor(147, 51, 234) // Purple
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text(title, 20, y)
|
||||
doc.setFont('helvetica', 'normal')
|
||||
return y + 10
|
||||
}
|
||||
|
||||
export function addSubsectionTitle(doc: jsPDF, title: string, y: number): number {
|
||||
doc.setFontSize(11)
|
||||
doc.setTextColor(60)
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text(title, 25, y)
|
||||
doc.setFont('helvetica', 'normal')
|
||||
return y + 7
|
||||
}
|
||||
|
||||
export function addText(doc: jsPDF, text: string, x: number, y: number, maxWidth: number = 170): number {
|
||||
doc.setFontSize(10)
|
||||
doc.setTextColor(60)
|
||||
const lines = doc.splitTextToSize(text, maxWidth)
|
||||
doc.text(lines, x, y)
|
||||
return y + lines.length * 5
|
||||
}
|
||||
|
||||
export function checkPageBreak(doc: jsPDF, y: number, requiredSpace: number = 40): number {
|
||||
const pageHeight = doc.internal.pageSize.getHeight()
|
||||
if (y + requiredSpace > pageHeight - 25) {
|
||||
doc.addPage()
|
||||
return 30
|
||||
}
|
||||
return y
|
||||
}
|
||||
312
admin-lehrer/lib/sdk/export-pdf.ts
Normal file
312
admin-lehrer/lib/sdk/export-pdf.ts
Normal file
@@ -0,0 +1,312 @@
|
||||
/**
|
||||
* PDF Export
|
||||
* Generates a multi-page compliance report as PDF
|
||||
*/
|
||||
|
||||
import jsPDF from 'jspdf'
|
||||
import { SDKState, SDK_STEPS } from './types'
|
||||
import { ExportOptions, DEFAULT_OPTIONS, LABELS_DE } from './export-types'
|
||||
import {
|
||||
formatDate,
|
||||
addHeader,
|
||||
addFooter,
|
||||
addSectionTitle,
|
||||
addSubsectionTitle,
|
||||
addText,
|
||||
checkPageBreak,
|
||||
} from './export-pdf-helpers'
|
||||
|
||||
export async function exportToPDF(state: SDKState, options: ExportOptions = {}): Promise<Blob> {
|
||||
const opts = { ...DEFAULT_OPTIONS, ...options }
|
||||
const doc = new jsPDF()
|
||||
|
||||
let y = 30
|
||||
const pageWidth = doc.internal.pageSize.getWidth()
|
||||
|
||||
// ==========================================================================
|
||||
// Title Page
|
||||
// ==========================================================================
|
||||
|
||||
// Logo/Title area
|
||||
doc.setFillColor(147, 51, 234)
|
||||
doc.rect(0, 0, pageWidth, 60, 'F')
|
||||
|
||||
doc.setFontSize(24)
|
||||
doc.setTextColor(255)
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text(LABELS_DE.title, 20, 35)
|
||||
|
||||
doc.setFontSize(14)
|
||||
doc.setFont('helvetica', 'normal')
|
||||
doc.text(LABELS_DE.subtitle, 20, 48)
|
||||
|
||||
// Reset for content
|
||||
y = 80
|
||||
|
||||
// Summary box
|
||||
doc.setDrawColor(200)
|
||||
doc.setFillColor(249, 250, 251)
|
||||
doc.roundedRect(20, y, pageWidth - 40, 50, 3, 3, 'FD')
|
||||
|
||||
y += 15
|
||||
doc.setFontSize(12)
|
||||
doc.setTextColor(60)
|
||||
doc.text(`${LABELS_DE.generatedAt}: ${formatDate(new Date())}`, 30, y)
|
||||
|
||||
y += 10
|
||||
doc.text(`Tenant ID: ${state.tenantId}`, 30, y)
|
||||
|
||||
y += 10
|
||||
doc.text(`Version: ${state.version}`, 30, y)
|
||||
|
||||
y += 10
|
||||
const completedSteps = state.completedSteps.length
|
||||
const totalSteps = SDK_STEPS.length
|
||||
doc.text(`${LABELS_DE.progress}: ${completedSteps}/${totalSteps} Schritte (${Math.round(completedSteps / totalSteps * 100)}%)`, 30, y)
|
||||
|
||||
y += 30
|
||||
|
||||
// Table of Contents
|
||||
y = addSectionTitle(doc, 'Inhaltsverzeichnis', y)
|
||||
|
||||
const tocItems = [
|
||||
{ title: 'Zusammenfassung', page: 2 },
|
||||
{ title: 'Phase 1: Compliance Assessment', page: 3 },
|
||||
{ title: 'Phase 2: Dokumentengenerierung', page: 4 },
|
||||
{ title: 'Risiken & Controls', page: 5 },
|
||||
{ title: 'Checkpoints', page: 6 },
|
||||
]
|
||||
|
||||
doc.setFontSize(10)
|
||||
doc.setTextColor(80)
|
||||
tocItems.forEach((item, idx) => {
|
||||
doc.text(`${idx + 1}. ${item.title}`, 25, y)
|
||||
doc.text(`${item.page}`, pageWidth - 30, y, { align: 'right' })
|
||||
y += 7
|
||||
})
|
||||
|
||||
// ==========================================================================
|
||||
// Summary Page
|
||||
// ==========================================================================
|
||||
|
||||
doc.addPage()
|
||||
y = 30
|
||||
|
||||
y = addSectionTitle(doc, LABELS_DE.summary, y)
|
||||
|
||||
// Progress overview
|
||||
doc.setFillColor(249, 250, 251)
|
||||
doc.roundedRect(20, y, pageWidth - 40, 40, 3, 3, 'F')
|
||||
|
||||
y += 15
|
||||
const phase1Steps = SDK_STEPS.filter(s => s.phase === 1)
|
||||
const phase2Steps = SDK_STEPS.filter(s => s.phase === 2)
|
||||
const phase1Completed = phase1Steps.filter(s => state.completedSteps.includes(s.id)).length
|
||||
const phase2Completed = phase2Steps.filter(s => state.completedSteps.includes(s.id)).length
|
||||
|
||||
doc.setFontSize(10)
|
||||
doc.setTextColor(60)
|
||||
doc.text(`${LABELS_DE.phase1}: ${phase1Completed}/${phase1Steps.length} ${LABELS_DE.completed}`, 30, y)
|
||||
y += 8
|
||||
doc.text(`${LABELS_DE.phase2}: ${phase2Completed}/${phase2Steps.length} ${LABELS_DE.completed}`, 30, y)
|
||||
|
||||
y += 25
|
||||
|
||||
// Key metrics
|
||||
y = addSubsectionTitle(doc, 'Kennzahlen', y)
|
||||
|
||||
const metrics = [
|
||||
{ label: 'Use Cases', value: state.useCases.length },
|
||||
{ label: 'Risiken identifiziert', value: state.risks.length },
|
||||
{ label: 'Controls definiert', value: state.controls.length },
|
||||
{ label: 'Anforderungen', value: state.requirements.length },
|
||||
{ label: 'Nachweise', value: state.evidence.length },
|
||||
]
|
||||
|
||||
metrics.forEach(metric => {
|
||||
doc.text(`${metric.label}: ${metric.value}`, 30, y)
|
||||
y += 7
|
||||
})
|
||||
|
||||
// ==========================================================================
|
||||
// Use Cases
|
||||
// ==========================================================================
|
||||
|
||||
y += 10
|
||||
y = checkPageBreak(doc, y)
|
||||
y = addSectionTitle(doc, LABELS_DE.useCases, y)
|
||||
|
||||
if (state.useCases.length === 0) {
|
||||
y = addText(doc, LABELS_DE.noData, 25, y)
|
||||
} else {
|
||||
state.useCases.forEach((uc, idx) => {
|
||||
y = checkPageBreak(doc, y, 50)
|
||||
|
||||
doc.setFillColor(249, 250, 251)
|
||||
doc.roundedRect(20, y - 5, pageWidth - 40, 35, 2, 2, 'F')
|
||||
|
||||
doc.setFontSize(11)
|
||||
doc.setTextColor(40)
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text(`${idx + 1}. ${uc.name}`, 25, y + 5)
|
||||
doc.setFont('helvetica', 'normal')
|
||||
|
||||
doc.setFontSize(9)
|
||||
doc.setTextColor(100)
|
||||
const ucStatus = uc.stepsCompleted === uc.steps.length ? LABELS_DE.completed : `${uc.stepsCompleted}/${uc.steps.length} Schritte`
|
||||
doc.text(`ID: ${uc.id} | ${LABELS_DE.status}: ${ucStatus}`, 25, y + 13)
|
||||
|
||||
if (uc.description) {
|
||||
y = addText(doc, uc.description, 25, y + 21, 160)
|
||||
}
|
||||
|
||||
y += 40
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Risks
|
||||
// ==========================================================================
|
||||
|
||||
doc.addPage()
|
||||
y = 30
|
||||
y = addSectionTitle(doc, LABELS_DE.risks, y)
|
||||
|
||||
if (state.risks.length === 0) {
|
||||
y = addText(doc, LABELS_DE.noData, 25, y)
|
||||
} else {
|
||||
// Sort by severity
|
||||
const sortedRisks = [...state.risks].sort((a, b) => {
|
||||
const order = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 }
|
||||
return (order[a.severity] || 4) - (order[b.severity] || 4)
|
||||
})
|
||||
|
||||
sortedRisks.forEach((risk, idx) => {
|
||||
y = checkPageBreak(doc, y, 45)
|
||||
|
||||
// Severity color
|
||||
const severityColors: Record<string, [number, number, number]> = {
|
||||
CRITICAL: [220, 38, 38],
|
||||
HIGH: [234, 88, 12],
|
||||
MEDIUM: [234, 179, 8],
|
||||
LOW: [34, 197, 94],
|
||||
}
|
||||
const color = severityColors[risk.severity] || [100, 100, 100]
|
||||
|
||||
doc.setFillColor(color[0], color[1], color[2])
|
||||
doc.rect(20, y - 3, 3, 30, 'F')
|
||||
|
||||
doc.setFontSize(11)
|
||||
doc.setTextColor(40)
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text(`${idx + 1}. ${risk.title}`, 28, y + 5)
|
||||
doc.setFont('helvetica', 'normal')
|
||||
|
||||
doc.setFontSize(9)
|
||||
doc.setTextColor(100)
|
||||
doc.text(`${LABELS_DE.severity}: ${risk.severity} | ${LABELS_DE.category}: ${risk.category}`, 28, y + 13)
|
||||
|
||||
if (risk.description) {
|
||||
y = addText(doc, risk.description, 28, y + 21, 155)
|
||||
}
|
||||
|
||||
if (risk.mitigation && risk.mitigation.length > 0) {
|
||||
y += 5
|
||||
doc.setFontSize(9)
|
||||
doc.setTextColor(34, 197, 94)
|
||||
doc.text(`${LABELS_DE.mitigation}: ${risk.mitigation.join(', ')}`, 28, y)
|
||||
}
|
||||
|
||||
y += 15
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Controls
|
||||
// ==========================================================================
|
||||
|
||||
doc.addPage()
|
||||
y = 30
|
||||
y = addSectionTitle(doc, LABELS_DE.controls, y)
|
||||
|
||||
if (state.controls.length === 0) {
|
||||
y = addText(doc, LABELS_DE.noData, 25, y)
|
||||
} else {
|
||||
state.controls.forEach((ctrl, idx) => {
|
||||
y = checkPageBreak(doc, y, 35)
|
||||
|
||||
doc.setFillColor(249, 250, 251)
|
||||
doc.roundedRect(20, y - 5, pageWidth - 40, 28, 2, 2, 'F')
|
||||
|
||||
doc.setFontSize(10)
|
||||
doc.setTextColor(40)
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text(`${idx + 1}. ${ctrl.name}`, 25, y + 5)
|
||||
doc.setFont('helvetica', 'normal')
|
||||
|
||||
doc.setFontSize(9)
|
||||
doc.setTextColor(100)
|
||||
doc.text(`${LABELS_DE.category}: ${ctrl.category} | ${LABELS_DE.implementation}: ${ctrl.implementationStatus || 'Nicht definiert'}`, 25, y + 13)
|
||||
|
||||
if (ctrl.description) {
|
||||
y = addText(doc, ctrl.description.substring(0, 150) + (ctrl.description.length > 150 ? '...' : ''), 25, y + 20, 160)
|
||||
}
|
||||
|
||||
y += 35
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Checkpoints
|
||||
// ==========================================================================
|
||||
|
||||
doc.addPage()
|
||||
y = 30
|
||||
y = addSectionTitle(doc, LABELS_DE.checkpoints, y)
|
||||
|
||||
const checkpointIds = Object.keys(state.checkpoints)
|
||||
|
||||
if (checkpointIds.length === 0) {
|
||||
y = addText(doc, LABELS_DE.noData, 25, y)
|
||||
} else {
|
||||
checkpointIds.forEach((cpId) => {
|
||||
const cp = state.checkpoints[cpId]
|
||||
y = checkPageBreak(doc, y, 25)
|
||||
|
||||
const statusColor = cp.passed ? [34, 197, 94] : [220, 38, 38]
|
||||
doc.setFillColor(statusColor[0], statusColor[1], statusColor[2])
|
||||
doc.circle(25, y + 2, 3, 'F')
|
||||
|
||||
doc.setFontSize(10)
|
||||
doc.setTextColor(40)
|
||||
doc.text(cpId, 35, y + 5)
|
||||
|
||||
doc.setFontSize(9)
|
||||
doc.setTextColor(100)
|
||||
doc.text(`${LABELS_DE.status}: ${cp.passed ? LABELS_DE.completed : LABELS_DE.pending}`, 35, y + 12)
|
||||
|
||||
if (cp.errors && cp.errors.length > 0) {
|
||||
doc.setTextColor(220, 38, 38)
|
||||
doc.text(`Fehler: ${cp.errors.map(e => e.message).join(', ')}`, 35, y + 19)
|
||||
y += 7
|
||||
}
|
||||
|
||||
y += 20
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Add page numbers
|
||||
// ==========================================================================
|
||||
|
||||
const pageCount = doc.getNumberOfPages()
|
||||
for (let i = 1; i <= pageCount; i++) {
|
||||
doc.setPage(i)
|
||||
if (i > 1) {
|
||||
addHeader(doc, LABELS_DE.title, i, pageCount)
|
||||
}
|
||||
addFooter(doc, state)
|
||||
}
|
||||
|
||||
return doc.output('blob')
|
||||
}
|
||||
53
admin-lehrer/lib/sdk/export-types.ts
Normal file
53
admin-lehrer/lib/sdk/export-types.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* SDK Export Types, Labels, and Constants
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// TYPES
|
||||
// =============================================================================
|
||||
|
||||
export interface ExportOptions {
|
||||
includeEvidence?: boolean
|
||||
includeDocuments?: boolean
|
||||
includeRawData?: boolean
|
||||
language?: 'de' | 'en'
|
||||
}
|
||||
|
||||
export const DEFAULT_OPTIONS: ExportOptions = {
|
||||
includeEvidence: true,
|
||||
includeDocuments: true,
|
||||
includeRawData: true,
|
||||
language: 'de',
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// LABELS (German)
|
||||
// =============================================================================
|
||||
|
||||
export const LABELS_DE = {
|
||||
title: 'AI Compliance SDK - Export',
|
||||
subtitle: 'Compliance-Dokumentation',
|
||||
generatedAt: 'Generiert am',
|
||||
page: 'Seite',
|
||||
summary: 'Zusammenfassung',
|
||||
progress: 'Fortschritt',
|
||||
phase1: 'Phase 1: Automatisches Compliance Assessment',
|
||||
phase2: 'Phase 2: Dokumentengenerierung',
|
||||
useCases: 'Use Cases',
|
||||
risks: 'Risiken',
|
||||
controls: 'Controls',
|
||||
requirements: 'Anforderungen',
|
||||
modules: 'Compliance-Module',
|
||||
evidence: 'Nachweise',
|
||||
checkpoints: 'Checkpoints',
|
||||
noData: 'Keine Daten vorhanden',
|
||||
status: 'Status',
|
||||
completed: 'Abgeschlossen',
|
||||
pending: 'Ausstehend',
|
||||
inProgress: 'In Bearbeitung',
|
||||
severity: 'Schweregrad',
|
||||
mitigation: 'Mitigation',
|
||||
description: 'Beschreibung',
|
||||
category: 'Kategorie',
|
||||
implementation: 'Implementierung',
|
||||
}
|
||||
278
admin-lehrer/lib/sdk/export-zip.ts
Normal file
278
admin-lehrer/lib/sdk/export-zip.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
/**
|
||||
* ZIP Export
|
||||
* Generates a structured ZIP archive with all compliance data
|
||||
*/
|
||||
|
||||
import JSZip from 'jszip'
|
||||
import { SDKState, SDK_STEPS } from './types'
|
||||
import { ExportOptions, DEFAULT_OPTIONS } from './export-types'
|
||||
import { formatDate } from './export-pdf-helpers'
|
||||
import { exportToPDF } from './export-pdf'
|
||||
|
||||
export async function exportToZIP(state: SDKState, options: ExportOptions = {}): Promise<Blob> {
|
||||
const opts = { ...DEFAULT_OPTIONS, ...options }
|
||||
const zip = new JSZip()
|
||||
|
||||
// Create folder structure
|
||||
const rootFolder = zip.folder('ai-compliance-sdk-export')
|
||||
if (!rootFolder) throw new Error('Failed to create ZIP folder')
|
||||
|
||||
const phase1Folder = rootFolder.folder('phase1-assessment')
|
||||
const phase2Folder = rootFolder.folder('phase2-documents')
|
||||
const dataFolder = rootFolder.folder('data')
|
||||
|
||||
// ==========================================================================
|
||||
// Main State JSON
|
||||
// ==========================================================================
|
||||
|
||||
if (opts.includeRawData && dataFolder) {
|
||||
dataFolder.file('state.json', JSON.stringify(state, null, 2))
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// README
|
||||
// ==========================================================================
|
||||
|
||||
const readmeContent = `# AI Compliance SDK Export
|
||||
|
||||
Generated: ${formatDate(new Date())}
|
||||
Tenant: ${state.tenantId}
|
||||
Version: ${state.version}
|
||||
|
||||
## Folder Structure
|
||||
|
||||
- **phase1-assessment/**: Compliance Assessment Ergebnisse
|
||||
- use-cases.json: Alle Use Cases
|
||||
- risks.json: Identifizierte Risiken
|
||||
- controls.json: Definierte Controls
|
||||
- requirements.json: Compliance-Anforderungen
|
||||
|
||||
- **phase2-documents/**: Generierte Dokumente
|
||||
- dsfa.json: Datenschutz-Folgenabschaetzung
|
||||
- toms.json: Technische und organisatorische Massnahmen
|
||||
- vvt.json: Verarbeitungsverzeichnis
|
||||
- documents.json: Rechtliche Dokumente
|
||||
|
||||
- **data/**: Rohdaten
|
||||
- state.json: Kompletter SDK State
|
||||
|
||||
## Progress
|
||||
|
||||
Phase 1: ${SDK_STEPS.filter(s => s.phase === 1 && state.completedSteps.includes(s.id)).length}/${SDK_STEPS.filter(s => s.phase === 1).length} completed
|
||||
Phase 2: ${SDK_STEPS.filter(s => s.phase === 2 && state.completedSteps.includes(s.id)).length}/${SDK_STEPS.filter(s => s.phase === 2).length} completed
|
||||
|
||||
## Key Metrics
|
||||
|
||||
- Use Cases: ${state.useCases.length}
|
||||
- Risks: ${state.risks.length}
|
||||
- Controls: ${state.controls.length}
|
||||
- Requirements: ${state.requirements.length}
|
||||
- Evidence: ${state.evidence.length}
|
||||
`
|
||||
|
||||
rootFolder.file('README.md', readmeContent)
|
||||
|
||||
// ==========================================================================
|
||||
// Phase 1 Files
|
||||
// ==========================================================================
|
||||
|
||||
if (phase1Folder) {
|
||||
// Use Cases
|
||||
phase1Folder.file('use-cases.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.useCases.length,
|
||||
useCases: state.useCases,
|
||||
}, null, 2))
|
||||
|
||||
// Risks
|
||||
phase1Folder.file('risks.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.risks.length,
|
||||
risks: state.risks,
|
||||
summary: {
|
||||
critical: state.risks.filter(r => r.severity === 'CRITICAL').length,
|
||||
high: state.risks.filter(r => r.severity === 'HIGH').length,
|
||||
medium: state.risks.filter(r => r.severity === 'MEDIUM').length,
|
||||
low: state.risks.filter(r => r.severity === 'LOW').length,
|
||||
},
|
||||
}, null, 2))
|
||||
|
||||
// Controls
|
||||
phase1Folder.file('controls.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.controls.length,
|
||||
controls: state.controls,
|
||||
}, null, 2))
|
||||
|
||||
// Requirements
|
||||
phase1Folder.file('requirements.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.requirements.length,
|
||||
requirements: state.requirements,
|
||||
}, null, 2))
|
||||
|
||||
// Modules
|
||||
phase1Folder.file('modules.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.modules.length,
|
||||
modules: state.modules,
|
||||
}, null, 2))
|
||||
|
||||
// Evidence
|
||||
if (opts.includeEvidence) {
|
||||
phase1Folder.file('evidence.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.evidence.length,
|
||||
evidence: state.evidence,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
// Checkpoints
|
||||
phase1Folder.file('checkpoints.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
checkpoints: state.checkpoints,
|
||||
}, null, 2))
|
||||
|
||||
// Screening
|
||||
if (state.screening) {
|
||||
phase1Folder.file('screening.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
screening: state.screening,
|
||||
}, null, 2))
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Phase 2 Files
|
||||
// ==========================================================================
|
||||
|
||||
if (phase2Folder) {
|
||||
// DSFA
|
||||
if (state.dsfa) {
|
||||
phase2Folder.file('dsfa.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
dsfa: state.dsfa,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
// TOMs
|
||||
phase2Folder.file('toms.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.toms.length,
|
||||
toms: state.toms,
|
||||
}, null, 2))
|
||||
|
||||
// VVT (Processing Activities)
|
||||
phase2Folder.file('vvt.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.vvt.length,
|
||||
processingActivities: state.vvt,
|
||||
}, null, 2))
|
||||
|
||||
// Legal Documents
|
||||
if (opts.includeDocuments) {
|
||||
phase2Folder.file('documents.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.documents.length,
|
||||
documents: state.documents,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
// Cookie Banner Config
|
||||
if (state.cookieBanner) {
|
||||
phase2Folder.file('cookie-banner.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
config: state.cookieBanner,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
// Retention Policies
|
||||
phase2Folder.file('retention-policies.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.retentionPolicies.length,
|
||||
policies: state.retentionPolicies,
|
||||
}, null, 2))
|
||||
|
||||
// AI Act Classification
|
||||
if (state.aiActClassification) {
|
||||
phase2Folder.file('ai-act-classification.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
classification: state.aiActClassification,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
// Obligations
|
||||
phase2Folder.file('obligations.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.obligations.length,
|
||||
obligations: state.obligations,
|
||||
}, null, 2))
|
||||
|
||||
// Consent Records
|
||||
phase2Folder.file('consents.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.consents.length,
|
||||
consents: state.consents,
|
||||
}, null, 2))
|
||||
|
||||
// DSR Config
|
||||
if (state.dsrConfig) {
|
||||
phase2Folder.file('dsr-config.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
config: state.dsrConfig,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
// Escalation Workflows
|
||||
phase2Folder.file('escalation-workflows.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.escalationWorkflows.length,
|
||||
workflows: state.escalationWorkflows,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Security Data
|
||||
// ==========================================================================
|
||||
|
||||
if (dataFolder) {
|
||||
if (state.sbom) {
|
||||
dataFolder.file('sbom.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
sbom: state.sbom,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
if (state.securityIssues.length > 0) {
|
||||
dataFolder.file('security-issues.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.securityIssues.length,
|
||||
issues: state.securityIssues,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
if (state.securityBacklog.length > 0) {
|
||||
dataFolder.file('security-backlog.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.securityBacklog.length,
|
||||
backlog: state.securityBacklog,
|
||||
}, null, 2))
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Generate PDF and include in ZIP
|
||||
// ==========================================================================
|
||||
|
||||
try {
|
||||
const pdfBlob = await exportToPDF(state, options)
|
||||
const pdfArrayBuffer = await pdfBlob.arrayBuffer()
|
||||
rootFolder.file('compliance-report.pdf', pdfArrayBuffer)
|
||||
} catch (error) {
|
||||
console.error('Failed to generate PDF for ZIP:', error)
|
||||
// Continue without PDF
|
||||
}
|
||||
|
||||
// Generate ZIP
|
||||
return zip.generateAsync({ type: 'blob', compression: 'DEFLATE' })
|
||||
}
|
||||
@@ -1,714 +1,20 @@
|
||||
/**
|
||||
* SDK Export Utilities
|
||||
* Handles PDF and ZIP export of SDK state and documents
|
||||
* Barrel module — re-exports PDF, ZIP, and helpers
|
||||
*/
|
||||
|
||||
import jsPDF from 'jspdf'
|
||||
import JSZip from 'jszip'
|
||||
import { SDKState, SDK_STEPS, getStepById } from './types'
|
||||
import { SDKState } from './types'
|
||||
import { ExportOptions } from './export-types'
|
||||
import { exportToPDF } from './export-pdf'
|
||||
import { exportToZIP } from './export-zip'
|
||||
|
||||
// Re-export all public API
|
||||
export type { ExportOptions } from './export-types'
|
||||
export { exportToPDF } from './export-pdf'
|
||||
export { exportToZIP } from './export-zip'
|
||||
|
||||
// =============================================================================
|
||||
// TYPES
|
||||
// =============================================================================
|
||||
|
||||
export interface ExportOptions {
|
||||
includeEvidence?: boolean
|
||||
includeDocuments?: boolean
|
||||
includeRawData?: boolean
|
||||
language?: 'de' | 'en'
|
||||
}
|
||||
|
||||
const DEFAULT_OPTIONS: ExportOptions = {
|
||||
includeEvidence: true,
|
||||
includeDocuments: true,
|
||||
includeRawData: true,
|
||||
language: 'de',
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// LABELS (German)
|
||||
// =============================================================================
|
||||
|
||||
const LABELS_DE = {
|
||||
title: 'AI Compliance SDK - Export',
|
||||
subtitle: 'Compliance-Dokumentation',
|
||||
generatedAt: 'Generiert am',
|
||||
page: 'Seite',
|
||||
summary: 'Zusammenfassung',
|
||||
progress: 'Fortschritt',
|
||||
phase1: 'Phase 1: Automatisches Compliance Assessment',
|
||||
phase2: 'Phase 2: Dokumentengenerierung',
|
||||
useCases: 'Use Cases',
|
||||
risks: 'Risiken',
|
||||
controls: 'Controls',
|
||||
requirements: 'Anforderungen',
|
||||
modules: 'Compliance-Module',
|
||||
evidence: 'Nachweise',
|
||||
checkpoints: 'Checkpoints',
|
||||
noData: 'Keine Daten vorhanden',
|
||||
status: 'Status',
|
||||
completed: 'Abgeschlossen',
|
||||
pending: 'Ausstehend',
|
||||
inProgress: 'In Bearbeitung',
|
||||
severity: 'Schweregrad',
|
||||
mitigation: 'Mitigation',
|
||||
description: 'Beschreibung',
|
||||
category: 'Kategorie',
|
||||
implementation: 'Implementierung',
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PDF EXPORT
|
||||
// =============================================================================
|
||||
|
||||
function formatDate(date: Date | string | undefined): string {
|
||||
if (!date) return '-'
|
||||
const d = typeof date === 'string' ? new Date(date) : date
|
||||
return d.toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
function addHeader(doc: jsPDF, title: string, pageNum: number, totalPages: number): void {
|
||||
const pageWidth = doc.internal.pageSize.getWidth()
|
||||
|
||||
// Header line
|
||||
doc.setDrawColor(147, 51, 234) // Purple
|
||||
doc.setLineWidth(0.5)
|
||||
doc.line(20, 15, pageWidth - 20, 15)
|
||||
|
||||
// Title
|
||||
doc.setFontSize(10)
|
||||
doc.setTextColor(100)
|
||||
doc.text(title, 20, 12)
|
||||
|
||||
// Page number
|
||||
doc.text(`${LABELS_DE.page} ${pageNum}/${totalPages}`, pageWidth - 40, 12)
|
||||
}
|
||||
|
||||
function addFooter(doc: jsPDF, state: SDKState): void {
|
||||
const pageWidth = doc.internal.pageSize.getWidth()
|
||||
const pageHeight = doc.internal.pageSize.getHeight()
|
||||
|
||||
// Footer line
|
||||
doc.setDrawColor(200)
|
||||
doc.setLineWidth(0.3)
|
||||
doc.line(20, pageHeight - 15, pageWidth - 20, pageHeight - 15)
|
||||
|
||||
// Footer text
|
||||
doc.setFontSize(8)
|
||||
doc.setTextColor(150)
|
||||
doc.text(`Tenant: ${state.tenantId} | ${LABELS_DE.generatedAt}: ${formatDate(new Date())}`, 20, pageHeight - 10)
|
||||
}
|
||||
|
||||
function addSectionTitle(doc: jsPDF, title: string, y: number): number {
|
||||
doc.setFontSize(14)
|
||||
doc.setTextColor(147, 51, 234) // Purple
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text(title, 20, y)
|
||||
doc.setFont('helvetica', 'normal')
|
||||
return y + 10
|
||||
}
|
||||
|
||||
function addSubsectionTitle(doc: jsPDF, title: string, y: number): number {
|
||||
doc.setFontSize(11)
|
||||
doc.setTextColor(60)
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text(title, 25, y)
|
||||
doc.setFont('helvetica', 'normal')
|
||||
return y + 7
|
||||
}
|
||||
|
||||
function addText(doc: jsPDF, text: string, x: number, y: number, maxWidth: number = 170): number {
|
||||
doc.setFontSize(10)
|
||||
doc.setTextColor(60)
|
||||
const lines = doc.splitTextToSize(text, maxWidth)
|
||||
doc.text(lines, x, y)
|
||||
return y + lines.length * 5
|
||||
}
|
||||
|
||||
function checkPageBreak(doc: jsPDF, y: number, requiredSpace: number = 40): number {
|
||||
const pageHeight = doc.internal.pageSize.getHeight()
|
||||
if (y + requiredSpace > pageHeight - 25) {
|
||||
doc.addPage()
|
||||
return 30
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
export async function exportToPDF(state: SDKState, options: ExportOptions = {}): Promise<Blob> {
|
||||
const opts = { ...DEFAULT_OPTIONS, ...options }
|
||||
const doc = new jsPDF()
|
||||
|
||||
let y = 30
|
||||
const pageWidth = doc.internal.pageSize.getWidth()
|
||||
|
||||
// ==========================================================================
|
||||
// Title Page
|
||||
// ==========================================================================
|
||||
|
||||
// Logo/Title area
|
||||
doc.setFillColor(147, 51, 234)
|
||||
doc.rect(0, 0, pageWidth, 60, 'F')
|
||||
|
||||
doc.setFontSize(24)
|
||||
doc.setTextColor(255)
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text(LABELS_DE.title, 20, 35)
|
||||
|
||||
doc.setFontSize(14)
|
||||
doc.setFont('helvetica', 'normal')
|
||||
doc.text(LABELS_DE.subtitle, 20, 48)
|
||||
|
||||
// Reset for content
|
||||
y = 80
|
||||
|
||||
// Summary box
|
||||
doc.setDrawColor(200)
|
||||
doc.setFillColor(249, 250, 251)
|
||||
doc.roundedRect(20, y, pageWidth - 40, 50, 3, 3, 'FD')
|
||||
|
||||
y += 15
|
||||
doc.setFontSize(12)
|
||||
doc.setTextColor(60)
|
||||
doc.text(`${LABELS_DE.generatedAt}: ${formatDate(new Date())}`, 30, y)
|
||||
|
||||
y += 10
|
||||
doc.text(`Tenant ID: ${state.tenantId}`, 30, y)
|
||||
|
||||
y += 10
|
||||
doc.text(`Version: ${state.version}`, 30, y)
|
||||
|
||||
y += 10
|
||||
const completedSteps = state.completedSteps.length
|
||||
const totalSteps = SDK_STEPS.length
|
||||
doc.text(`${LABELS_DE.progress}: ${completedSteps}/${totalSteps} Schritte (${Math.round(completedSteps / totalSteps * 100)}%)`, 30, y)
|
||||
|
||||
y += 30
|
||||
|
||||
// Table of Contents
|
||||
y = addSectionTitle(doc, 'Inhaltsverzeichnis', y)
|
||||
|
||||
const tocItems = [
|
||||
{ title: 'Zusammenfassung', page: 2 },
|
||||
{ title: 'Phase 1: Compliance Assessment', page: 3 },
|
||||
{ title: 'Phase 2: Dokumentengenerierung', page: 4 },
|
||||
{ title: 'Risiken & Controls', page: 5 },
|
||||
{ title: 'Checkpoints', page: 6 },
|
||||
]
|
||||
|
||||
doc.setFontSize(10)
|
||||
doc.setTextColor(80)
|
||||
tocItems.forEach((item, idx) => {
|
||||
doc.text(`${idx + 1}. ${item.title}`, 25, y)
|
||||
doc.text(`${item.page}`, pageWidth - 30, y, { align: 'right' })
|
||||
y += 7
|
||||
})
|
||||
|
||||
// ==========================================================================
|
||||
// Summary Page
|
||||
// ==========================================================================
|
||||
|
||||
doc.addPage()
|
||||
y = 30
|
||||
|
||||
y = addSectionTitle(doc, LABELS_DE.summary, y)
|
||||
|
||||
// Progress overview
|
||||
doc.setFillColor(249, 250, 251)
|
||||
doc.roundedRect(20, y, pageWidth - 40, 40, 3, 3, 'F')
|
||||
|
||||
y += 15
|
||||
const phase1Steps = SDK_STEPS.filter(s => s.phase === 1)
|
||||
const phase2Steps = SDK_STEPS.filter(s => s.phase === 2)
|
||||
const phase1Completed = phase1Steps.filter(s => state.completedSteps.includes(s.id)).length
|
||||
const phase2Completed = phase2Steps.filter(s => state.completedSteps.includes(s.id)).length
|
||||
|
||||
doc.setFontSize(10)
|
||||
doc.setTextColor(60)
|
||||
doc.text(`${LABELS_DE.phase1}: ${phase1Completed}/${phase1Steps.length} ${LABELS_DE.completed}`, 30, y)
|
||||
y += 8
|
||||
doc.text(`${LABELS_DE.phase2}: ${phase2Completed}/${phase2Steps.length} ${LABELS_DE.completed}`, 30, y)
|
||||
|
||||
y += 25
|
||||
|
||||
// Key metrics
|
||||
y = addSubsectionTitle(doc, 'Kennzahlen', y)
|
||||
|
||||
const metrics = [
|
||||
{ label: 'Use Cases', value: state.useCases.length },
|
||||
{ label: 'Risiken identifiziert', value: state.risks.length },
|
||||
{ label: 'Controls definiert', value: state.controls.length },
|
||||
{ label: 'Anforderungen', value: state.requirements.length },
|
||||
{ label: 'Nachweise', value: state.evidence.length },
|
||||
]
|
||||
|
||||
metrics.forEach(metric => {
|
||||
doc.text(`${metric.label}: ${metric.value}`, 30, y)
|
||||
y += 7
|
||||
})
|
||||
|
||||
// ==========================================================================
|
||||
// Use Cases
|
||||
// ==========================================================================
|
||||
|
||||
y += 10
|
||||
y = checkPageBreak(doc, y)
|
||||
y = addSectionTitle(doc, LABELS_DE.useCases, y)
|
||||
|
||||
if (state.useCases.length === 0) {
|
||||
y = addText(doc, LABELS_DE.noData, 25, y)
|
||||
} else {
|
||||
state.useCases.forEach((uc, idx) => {
|
||||
y = checkPageBreak(doc, y, 50)
|
||||
|
||||
doc.setFillColor(249, 250, 251)
|
||||
doc.roundedRect(20, y - 5, pageWidth - 40, 35, 2, 2, 'F')
|
||||
|
||||
doc.setFontSize(11)
|
||||
doc.setTextColor(40)
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text(`${idx + 1}. ${uc.name}`, 25, y + 5)
|
||||
doc.setFont('helvetica', 'normal')
|
||||
|
||||
doc.setFontSize(9)
|
||||
doc.setTextColor(100)
|
||||
const ucStatus = uc.stepsCompleted === uc.steps.length ? LABELS_DE.completed : `${uc.stepsCompleted}/${uc.steps.length} Schritte`
|
||||
doc.text(`ID: ${uc.id} | ${LABELS_DE.status}: ${ucStatus}`, 25, y + 13)
|
||||
|
||||
if (uc.description) {
|
||||
y = addText(doc, uc.description, 25, y + 21, 160)
|
||||
}
|
||||
|
||||
y += 40
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Risks
|
||||
// ==========================================================================
|
||||
|
||||
doc.addPage()
|
||||
y = 30
|
||||
y = addSectionTitle(doc, LABELS_DE.risks, y)
|
||||
|
||||
if (state.risks.length === 0) {
|
||||
y = addText(doc, LABELS_DE.noData, 25, y)
|
||||
} else {
|
||||
// Sort by severity
|
||||
const sortedRisks = [...state.risks].sort((a, b) => {
|
||||
const order = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 }
|
||||
return (order[a.severity] || 4) - (order[b.severity] || 4)
|
||||
})
|
||||
|
||||
sortedRisks.forEach((risk, idx) => {
|
||||
y = checkPageBreak(doc, y, 45)
|
||||
|
||||
// Severity color
|
||||
const severityColors: Record<string, [number, number, number]> = {
|
||||
CRITICAL: [220, 38, 38],
|
||||
HIGH: [234, 88, 12],
|
||||
MEDIUM: [234, 179, 8],
|
||||
LOW: [34, 197, 94],
|
||||
}
|
||||
const color = severityColors[risk.severity] || [100, 100, 100]
|
||||
|
||||
doc.setFillColor(color[0], color[1], color[2])
|
||||
doc.rect(20, y - 3, 3, 30, 'F')
|
||||
|
||||
doc.setFontSize(11)
|
||||
doc.setTextColor(40)
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text(`${idx + 1}. ${risk.title}`, 28, y + 5)
|
||||
doc.setFont('helvetica', 'normal')
|
||||
|
||||
doc.setFontSize(9)
|
||||
doc.setTextColor(100)
|
||||
doc.text(`${LABELS_DE.severity}: ${risk.severity} | ${LABELS_DE.category}: ${risk.category}`, 28, y + 13)
|
||||
|
||||
if (risk.description) {
|
||||
y = addText(doc, risk.description, 28, y + 21, 155)
|
||||
}
|
||||
|
||||
if (risk.mitigation && risk.mitigation.length > 0) {
|
||||
y += 5
|
||||
doc.setFontSize(9)
|
||||
doc.setTextColor(34, 197, 94)
|
||||
doc.text(`${LABELS_DE.mitigation}: ${risk.mitigation.join(', ')}`, 28, y)
|
||||
}
|
||||
|
||||
y += 15
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Controls
|
||||
// ==========================================================================
|
||||
|
||||
doc.addPage()
|
||||
y = 30
|
||||
y = addSectionTitle(doc, LABELS_DE.controls, y)
|
||||
|
||||
if (state.controls.length === 0) {
|
||||
y = addText(doc, LABELS_DE.noData, 25, y)
|
||||
} else {
|
||||
state.controls.forEach((ctrl, idx) => {
|
||||
y = checkPageBreak(doc, y, 35)
|
||||
|
||||
doc.setFillColor(249, 250, 251)
|
||||
doc.roundedRect(20, y - 5, pageWidth - 40, 28, 2, 2, 'F')
|
||||
|
||||
doc.setFontSize(10)
|
||||
doc.setTextColor(40)
|
||||
doc.setFont('helvetica', 'bold')
|
||||
doc.text(`${idx + 1}. ${ctrl.name}`, 25, y + 5)
|
||||
doc.setFont('helvetica', 'normal')
|
||||
|
||||
doc.setFontSize(9)
|
||||
doc.setTextColor(100)
|
||||
doc.text(`${LABELS_DE.category}: ${ctrl.category} | ${LABELS_DE.implementation}: ${ctrl.implementationStatus || 'Nicht definiert'}`, 25, y + 13)
|
||||
|
||||
if (ctrl.description) {
|
||||
y = addText(doc, ctrl.description.substring(0, 150) + (ctrl.description.length > 150 ? '...' : ''), 25, y + 20, 160)
|
||||
}
|
||||
|
||||
y += 35
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Checkpoints
|
||||
// ==========================================================================
|
||||
|
||||
doc.addPage()
|
||||
y = 30
|
||||
y = addSectionTitle(doc, LABELS_DE.checkpoints, y)
|
||||
|
||||
const checkpointIds = Object.keys(state.checkpoints)
|
||||
|
||||
if (checkpointIds.length === 0) {
|
||||
y = addText(doc, LABELS_DE.noData, 25, y)
|
||||
} else {
|
||||
checkpointIds.forEach((cpId) => {
|
||||
const cp = state.checkpoints[cpId]
|
||||
y = checkPageBreak(doc, y, 25)
|
||||
|
||||
const statusColor = cp.passed ? [34, 197, 94] : [220, 38, 38]
|
||||
doc.setFillColor(statusColor[0], statusColor[1], statusColor[2])
|
||||
doc.circle(25, y + 2, 3, 'F')
|
||||
|
||||
doc.setFontSize(10)
|
||||
doc.setTextColor(40)
|
||||
doc.text(cpId, 35, y + 5)
|
||||
|
||||
doc.setFontSize(9)
|
||||
doc.setTextColor(100)
|
||||
doc.text(`${LABELS_DE.status}: ${cp.passed ? LABELS_DE.completed : LABELS_DE.pending}`, 35, y + 12)
|
||||
|
||||
if (cp.errors && cp.errors.length > 0) {
|
||||
doc.setTextColor(220, 38, 38)
|
||||
doc.text(`Fehler: ${cp.errors.map(e => e.message).join(', ')}`, 35, y + 19)
|
||||
y += 7
|
||||
}
|
||||
|
||||
y += 20
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Add page numbers
|
||||
// ==========================================================================
|
||||
|
||||
const pageCount = doc.getNumberOfPages()
|
||||
for (let i = 1; i <= pageCount; i++) {
|
||||
doc.setPage(i)
|
||||
if (i > 1) {
|
||||
addHeader(doc, LABELS_DE.title, i, pageCount)
|
||||
}
|
||||
addFooter(doc, state)
|
||||
}
|
||||
|
||||
return doc.output('blob')
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ZIP EXPORT
|
||||
// =============================================================================
|
||||
|
||||
export async function exportToZIP(state: SDKState, options: ExportOptions = {}): Promise<Blob> {
|
||||
const opts = { ...DEFAULT_OPTIONS, ...options }
|
||||
const zip = new JSZip()
|
||||
|
||||
// Create folder structure
|
||||
const rootFolder = zip.folder('ai-compliance-sdk-export')
|
||||
if (!rootFolder) throw new Error('Failed to create ZIP folder')
|
||||
|
||||
const phase1Folder = rootFolder.folder('phase1-assessment')
|
||||
const phase2Folder = rootFolder.folder('phase2-documents')
|
||||
const dataFolder = rootFolder.folder('data')
|
||||
|
||||
// ==========================================================================
|
||||
// Main State JSON
|
||||
// ==========================================================================
|
||||
|
||||
if (opts.includeRawData && dataFolder) {
|
||||
dataFolder.file('state.json', JSON.stringify(state, null, 2))
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// README
|
||||
// ==========================================================================
|
||||
|
||||
const readmeContent = `# AI Compliance SDK Export
|
||||
|
||||
Generated: ${formatDate(new Date())}
|
||||
Tenant: ${state.tenantId}
|
||||
Version: ${state.version}
|
||||
|
||||
## Folder Structure
|
||||
|
||||
- **phase1-assessment/**: Compliance Assessment Ergebnisse
|
||||
- use-cases.json: Alle Use Cases
|
||||
- risks.json: Identifizierte Risiken
|
||||
- controls.json: Definierte Controls
|
||||
- requirements.json: Compliance-Anforderungen
|
||||
|
||||
- **phase2-documents/**: Generierte Dokumente
|
||||
- dsfa.json: Datenschutz-Folgenabschaetzung
|
||||
- toms.json: Technische und organisatorische Massnahmen
|
||||
- vvt.json: Verarbeitungsverzeichnis
|
||||
- documents.json: Rechtliche Dokumente
|
||||
|
||||
- **data/**: Rohdaten
|
||||
- state.json: Kompletter SDK State
|
||||
|
||||
## Progress
|
||||
|
||||
Phase 1: ${SDK_STEPS.filter(s => s.phase === 1 && state.completedSteps.includes(s.id)).length}/${SDK_STEPS.filter(s => s.phase === 1).length} completed
|
||||
Phase 2: ${SDK_STEPS.filter(s => s.phase === 2 && state.completedSteps.includes(s.id)).length}/${SDK_STEPS.filter(s => s.phase === 2).length} completed
|
||||
|
||||
## Key Metrics
|
||||
|
||||
- Use Cases: ${state.useCases.length}
|
||||
- Risks: ${state.risks.length}
|
||||
- Controls: ${state.controls.length}
|
||||
- Requirements: ${state.requirements.length}
|
||||
- Evidence: ${state.evidence.length}
|
||||
`
|
||||
|
||||
rootFolder.file('README.md', readmeContent)
|
||||
|
||||
// ==========================================================================
|
||||
// Phase 1 Files
|
||||
// ==========================================================================
|
||||
|
||||
if (phase1Folder) {
|
||||
// Use Cases
|
||||
phase1Folder.file('use-cases.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.useCases.length,
|
||||
useCases: state.useCases,
|
||||
}, null, 2))
|
||||
|
||||
// Risks
|
||||
phase1Folder.file('risks.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.risks.length,
|
||||
risks: state.risks,
|
||||
summary: {
|
||||
critical: state.risks.filter(r => r.severity === 'CRITICAL').length,
|
||||
high: state.risks.filter(r => r.severity === 'HIGH').length,
|
||||
medium: state.risks.filter(r => r.severity === 'MEDIUM').length,
|
||||
low: state.risks.filter(r => r.severity === 'LOW').length,
|
||||
},
|
||||
}, null, 2))
|
||||
|
||||
// Controls
|
||||
phase1Folder.file('controls.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.controls.length,
|
||||
controls: state.controls,
|
||||
}, null, 2))
|
||||
|
||||
// Requirements
|
||||
phase1Folder.file('requirements.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.requirements.length,
|
||||
requirements: state.requirements,
|
||||
}, null, 2))
|
||||
|
||||
// Modules
|
||||
phase1Folder.file('modules.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.modules.length,
|
||||
modules: state.modules,
|
||||
}, null, 2))
|
||||
|
||||
// Evidence
|
||||
if (opts.includeEvidence) {
|
||||
phase1Folder.file('evidence.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.evidence.length,
|
||||
evidence: state.evidence,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
// Checkpoints
|
||||
phase1Folder.file('checkpoints.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
checkpoints: state.checkpoints,
|
||||
}, null, 2))
|
||||
|
||||
// Screening
|
||||
if (state.screening) {
|
||||
phase1Folder.file('screening.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
screening: state.screening,
|
||||
}, null, 2))
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Phase 2 Files
|
||||
// ==========================================================================
|
||||
|
||||
if (phase2Folder) {
|
||||
// DSFA
|
||||
if (state.dsfa) {
|
||||
phase2Folder.file('dsfa.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
dsfa: state.dsfa,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
// TOMs
|
||||
phase2Folder.file('toms.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.toms.length,
|
||||
toms: state.toms,
|
||||
}, null, 2))
|
||||
|
||||
// VVT (Processing Activities)
|
||||
phase2Folder.file('vvt.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.vvt.length,
|
||||
processingActivities: state.vvt,
|
||||
}, null, 2))
|
||||
|
||||
// Legal Documents
|
||||
if (opts.includeDocuments) {
|
||||
phase2Folder.file('documents.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.documents.length,
|
||||
documents: state.documents,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
// Cookie Banner Config
|
||||
if (state.cookieBanner) {
|
||||
phase2Folder.file('cookie-banner.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
config: state.cookieBanner,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
// Retention Policies
|
||||
phase2Folder.file('retention-policies.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.retentionPolicies.length,
|
||||
policies: state.retentionPolicies,
|
||||
}, null, 2))
|
||||
|
||||
// AI Act Classification
|
||||
if (state.aiActClassification) {
|
||||
phase2Folder.file('ai-act-classification.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
classification: state.aiActClassification,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
// Obligations
|
||||
phase2Folder.file('obligations.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.obligations.length,
|
||||
obligations: state.obligations,
|
||||
}, null, 2))
|
||||
|
||||
// Consent Records
|
||||
phase2Folder.file('consents.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.consents.length,
|
||||
consents: state.consents,
|
||||
}, null, 2))
|
||||
|
||||
// DSR Config
|
||||
if (state.dsrConfig) {
|
||||
phase2Folder.file('dsr-config.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
config: state.dsrConfig,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
// Escalation Workflows
|
||||
phase2Folder.file('escalation-workflows.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.escalationWorkflows.length,
|
||||
workflows: state.escalationWorkflows,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Security Data
|
||||
// ==========================================================================
|
||||
|
||||
if (dataFolder) {
|
||||
if (state.sbom) {
|
||||
dataFolder.file('sbom.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
sbom: state.sbom,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
if (state.securityIssues.length > 0) {
|
||||
dataFolder.file('security-issues.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.securityIssues.length,
|
||||
issues: state.securityIssues,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
if (state.securityBacklog.length > 0) {
|
||||
dataFolder.file('security-backlog.json', JSON.stringify({
|
||||
exportedAt: new Date().toISOString(),
|
||||
count: state.securityBacklog.length,
|
||||
backlog: state.securityBacklog,
|
||||
}, null, 2))
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Generate PDF and include in ZIP
|
||||
// ==========================================================================
|
||||
|
||||
try {
|
||||
const pdfBlob = await exportToPDF(state, options)
|
||||
const pdfArrayBuffer = await pdfBlob.arrayBuffer()
|
||||
rootFolder.file('compliance-report.pdf', pdfArrayBuffer)
|
||||
} catch (error) {
|
||||
console.error('Failed to generate PDF for ZIP:', error)
|
||||
// Continue without PDF
|
||||
}
|
||||
|
||||
// Generate ZIP
|
||||
return zip.generateAsync({ type: 'blob', compression: 'DEFLATE' })
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// EXPORT HELPER
|
||||
// DOWNLOAD HELPER
|
||||
// =============================================================================
|
||||
|
||||
export async function downloadExport(
|
||||
|
||||
491
admin-lehrer/lib/sdk/vendor-compliance/context-actions.ts
Normal file
491
admin-lehrer/lib/sdk/vendor-compliance/context-actions.ts
Normal file
@@ -0,0 +1,491 @@
|
||||
import type React from 'react'
|
||||
import type {
|
||||
VendorComplianceAction,
|
||||
VendorComplianceState,
|
||||
ProcessingActivity,
|
||||
Vendor,
|
||||
ContractDocument,
|
||||
Finding,
|
||||
ControlInstance,
|
||||
ExportFormat,
|
||||
} from './types'
|
||||
|
||||
type Dispatch = React.Dispatch<VendorComplianceAction>
|
||||
|
||||
const API_BASE = '/api/sdk/v1/vendor-compliance'
|
||||
|
||||
// ==========================================
|
||||
// DATA LOADING
|
||||
// ==========================================
|
||||
|
||||
export async function loadAllData(dispatch: Dispatch): Promise<void> {
|
||||
dispatch({ type: 'SET_LOADING', payload: true })
|
||||
dispatch({ type: 'SET_ERROR', payload: null })
|
||||
|
||||
try {
|
||||
const [
|
||||
activitiesRes,
|
||||
vendorsRes,
|
||||
contractsRes,
|
||||
findingsRes,
|
||||
controlsRes,
|
||||
controlInstancesRes,
|
||||
] = await Promise.all([
|
||||
fetch(`${API_BASE}/processing-activities`),
|
||||
fetch(`${API_BASE}/vendors`),
|
||||
fetch(`${API_BASE}/contracts`),
|
||||
fetch(`${API_BASE}/findings`),
|
||||
fetch(`${API_BASE}/controls`),
|
||||
fetch(`${API_BASE}/control-instances`),
|
||||
])
|
||||
|
||||
if (activitiesRes.ok) {
|
||||
const data = await activitiesRes.json()
|
||||
dispatch({ type: 'SET_PROCESSING_ACTIVITIES', payload: data.data || [] })
|
||||
}
|
||||
|
||||
if (vendorsRes.ok) {
|
||||
const data = await vendorsRes.json()
|
||||
dispatch({ type: 'SET_VENDORS', payload: data.data || [] })
|
||||
}
|
||||
|
||||
if (contractsRes.ok) {
|
||||
const data = await contractsRes.json()
|
||||
dispatch({ type: 'SET_CONTRACTS', payload: data.data || [] })
|
||||
}
|
||||
|
||||
if (findingsRes.ok) {
|
||||
const data = await findingsRes.json()
|
||||
dispatch({ type: 'SET_FINDINGS', payload: data.data || [] })
|
||||
}
|
||||
|
||||
if (controlsRes.ok) {
|
||||
const data = await controlsRes.json()
|
||||
dispatch({ type: 'SET_CONTROLS', payload: data.data || [] })
|
||||
}
|
||||
|
||||
if (controlInstancesRes.ok) {
|
||||
const data = await controlInstancesRes.json()
|
||||
dispatch({ type: 'SET_CONTROL_INSTANCES', payload: data.data || [] })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load vendor compliance data:', error)
|
||||
dispatch({
|
||||
type: 'SET_ERROR',
|
||||
payload: 'Fehler beim Laden der Daten',
|
||||
})
|
||||
} finally {
|
||||
dispatch({ type: 'SET_LOADING', payload: false })
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// PROCESSING ACTIVITIES ACTIONS
|
||||
// ==========================================
|
||||
|
||||
export async function apiCreateProcessingActivity(
|
||||
dispatch: Dispatch,
|
||||
data: Omit<ProcessingActivity, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>
|
||||
): Promise<ProcessingActivity> {
|
||||
const response = await fetch(`${API_BASE}/processing-activities`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Erstellen der Verarbeitungstaetigkeit')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const activity = result.data
|
||||
|
||||
dispatch({ type: 'ADD_PROCESSING_ACTIVITY', payload: activity })
|
||||
|
||||
return activity
|
||||
}
|
||||
|
||||
export async function apiUpdateProcessingActivity(
|
||||
dispatch: Dispatch,
|
||||
id: string,
|
||||
data: Partial<ProcessingActivity>
|
||||
): Promise<void> {
|
||||
const response = await fetch(`${API_BASE}/processing-activities/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Aktualisieren der Verarbeitungstaetigkeit')
|
||||
}
|
||||
|
||||
dispatch({ type: 'UPDATE_PROCESSING_ACTIVITY', payload: { id, data } })
|
||||
}
|
||||
|
||||
export async function apiDeleteProcessingActivity(
|
||||
dispatch: Dispatch,
|
||||
id: string
|
||||
): Promise<void> {
|
||||
const response = await fetch(`${API_BASE}/processing-activities/${id}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Loeschen der Verarbeitungstaetigkeit')
|
||||
}
|
||||
|
||||
dispatch({ type: 'DELETE_PROCESSING_ACTIVITY', payload: id })
|
||||
}
|
||||
|
||||
export async function apiDuplicateProcessingActivity(
|
||||
dispatch: Dispatch,
|
||||
state: VendorComplianceState,
|
||||
id: string
|
||||
): Promise<ProcessingActivity> {
|
||||
const original = state.processingActivities.find((a) => a.id === id)
|
||||
if (!original) {
|
||||
throw new Error('Verarbeitungstaetigkeit nicht gefunden')
|
||||
}
|
||||
|
||||
const { id: _id, vvtId: _vvtId, createdAt: _createdAt, updatedAt: _updatedAt, tenantId: _tenantId, ...rest } = original
|
||||
|
||||
const newActivity = await apiCreateProcessingActivity(dispatch, {
|
||||
...rest,
|
||||
vvtId: '', // Will be generated by backend
|
||||
name: {
|
||||
de: `${original.name.de} (Kopie)`,
|
||||
en: `${original.name.en} (Copy)`,
|
||||
},
|
||||
status: 'DRAFT',
|
||||
})
|
||||
|
||||
return newActivity
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// VENDOR ACTIONS
|
||||
// ==========================================
|
||||
|
||||
export async function apiCreateVendor(
|
||||
dispatch: Dispatch,
|
||||
data: Omit<Vendor, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>
|
||||
): Promise<Vendor> {
|
||||
const response = await fetch(`${API_BASE}/vendors`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Erstellen des Vendors')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const vendor = result.data
|
||||
|
||||
dispatch({ type: 'ADD_VENDOR', payload: vendor })
|
||||
|
||||
return vendor
|
||||
}
|
||||
|
||||
export async function apiUpdateVendor(
|
||||
dispatch: Dispatch,
|
||||
id: string,
|
||||
data: Partial<Vendor>
|
||||
): Promise<void> {
|
||||
const response = await fetch(`${API_BASE}/vendors/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Aktualisieren des Vendors')
|
||||
}
|
||||
|
||||
dispatch({ type: 'UPDATE_VENDOR', payload: { id, data } })
|
||||
}
|
||||
|
||||
export async function apiDeleteVendor(
|
||||
dispatch: Dispatch,
|
||||
id: string
|
||||
): Promise<void> {
|
||||
const response = await fetch(`${API_BASE}/vendors/${id}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Loeschen des Vendors')
|
||||
}
|
||||
|
||||
dispatch({ type: 'DELETE_VENDOR', payload: id })
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// CONTRACT ACTIONS
|
||||
// ==========================================
|
||||
|
||||
export async function apiUploadContract(
|
||||
dispatch: Dispatch,
|
||||
state: VendorComplianceState,
|
||||
vendorId: string,
|
||||
file: File,
|
||||
metadata: Partial<ContractDocument>
|
||||
): Promise<ContractDocument> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('vendorId', vendorId)
|
||||
formData.append('metadata', JSON.stringify(metadata))
|
||||
|
||||
const response = await fetch(`${API_BASE}/contracts`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Hochladen des Vertrags')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const contract = result.data
|
||||
|
||||
dispatch({ type: 'ADD_CONTRACT', payload: contract })
|
||||
|
||||
// Update vendor's contracts list
|
||||
const vendor = state.vendors.find((v) => v.id === vendorId)
|
||||
if (vendor) {
|
||||
dispatch({
|
||||
type: 'UPDATE_VENDOR',
|
||||
payload: {
|
||||
id: vendorId,
|
||||
data: { contracts: [...vendor.contracts, contract.id] },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return contract
|
||||
}
|
||||
|
||||
export async function apiUpdateContract(
|
||||
dispatch: Dispatch,
|
||||
id: string,
|
||||
data: Partial<ContractDocument>
|
||||
): Promise<void> {
|
||||
const response = await fetch(`${API_BASE}/contracts/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Aktualisieren des Vertrags')
|
||||
}
|
||||
|
||||
dispatch({ type: 'UPDATE_CONTRACT', payload: { id, data } })
|
||||
}
|
||||
|
||||
export async function apiDeleteContract(
|
||||
dispatch: Dispatch,
|
||||
state: VendorComplianceState,
|
||||
id: string
|
||||
): Promise<void> {
|
||||
const contract = state.contracts.find((c) => c.id === id)
|
||||
|
||||
const response = await fetch(`${API_BASE}/contracts/${id}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Loeschen des Vertrags')
|
||||
}
|
||||
|
||||
dispatch({ type: 'DELETE_CONTRACT', payload: id })
|
||||
|
||||
// Update vendor's contracts list
|
||||
if (contract) {
|
||||
const vendor = state.vendors.find((v) => v.id === contract.vendorId)
|
||||
if (vendor) {
|
||||
dispatch({
|
||||
type: 'UPDATE_VENDOR',
|
||||
payload: {
|
||||
id: vendor.id,
|
||||
data: { contracts: vendor.contracts.filter((cId) => cId !== id) },
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiStartContractReview(
|
||||
dispatch: Dispatch,
|
||||
contractId: string
|
||||
): Promise<void> {
|
||||
dispatch({
|
||||
type: 'UPDATE_CONTRACT',
|
||||
payload: { id: contractId, data: { reviewStatus: 'IN_PROGRESS' } },
|
||||
})
|
||||
|
||||
const response = await fetch(`${API_BASE}/contracts/${contractId}/review`, {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
dispatch({
|
||||
type: 'UPDATE_CONTRACT',
|
||||
payload: { id: contractId, data: { reviewStatus: 'FAILED' } },
|
||||
})
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Starten der Vertragspruefung')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
// Update contract with review results
|
||||
dispatch({
|
||||
type: 'UPDATE_CONTRACT',
|
||||
payload: {
|
||||
id: contractId,
|
||||
data: {
|
||||
reviewStatus: 'COMPLETED',
|
||||
reviewCompletedAt: new Date(),
|
||||
complianceScore: result.data.complianceScore,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Add findings
|
||||
if (result.data.findings && result.data.findings.length > 0) {
|
||||
dispatch({ type: 'ADD_FINDINGS', payload: result.data.findings })
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// FINDINGS ACTIONS
|
||||
// ==========================================
|
||||
|
||||
export async function apiUpdateFinding(
|
||||
dispatch: Dispatch,
|
||||
id: string,
|
||||
data: Partial<Finding>
|
||||
): Promise<void> {
|
||||
const response = await fetch(`${API_BASE}/findings/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Aktualisieren des Findings')
|
||||
}
|
||||
|
||||
dispatch({ type: 'UPDATE_FINDING', payload: { id, data } })
|
||||
}
|
||||
|
||||
export async function apiResolveFinding(
|
||||
dispatch: Dispatch,
|
||||
id: string,
|
||||
resolution: string
|
||||
): Promise<void> {
|
||||
await apiUpdateFinding(dispatch, id, {
|
||||
status: 'RESOLVED',
|
||||
resolution,
|
||||
resolvedAt: new Date(),
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// CONTROL ACTIONS
|
||||
// ==========================================
|
||||
|
||||
export async function apiUpdateControlInstance(
|
||||
dispatch: Dispatch,
|
||||
id: string,
|
||||
data: Partial<ControlInstance>
|
||||
): Promise<void> {
|
||||
const response = await fetch(`${API_BASE}/control-instances/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Aktualisieren des Control-Status')
|
||||
}
|
||||
|
||||
dispatch({ type: 'UPDATE_CONTROL_INSTANCE', payload: { id, data } })
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// EXPORT ACTIONS
|
||||
// ==========================================
|
||||
|
||||
export async function apiExportVVT(
|
||||
format: ExportFormat,
|
||||
activityIds?: string[]
|
||||
): Promise<string> {
|
||||
const params = new URLSearchParams({ format })
|
||||
if (activityIds && activityIds.length > 0) {
|
||||
params.append('activityIds', activityIds.join(','))
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE}/export/vvt?${params}`)
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Exportieren des VVT')
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
export async function apiExportVendorAuditPack(
|
||||
vendorId: string,
|
||||
format: ExportFormat
|
||||
): Promise<string> {
|
||||
const params = new URLSearchParams({ format, vendorId })
|
||||
|
||||
const response = await fetch(`${API_BASE}/export/vendor-audit?${params}`)
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Exportieren des Vendor Audit Packs')
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
export async function apiExportRoPA(
|
||||
format: ExportFormat
|
||||
): Promise<string> {
|
||||
const params = new URLSearchParams({ format })
|
||||
|
||||
const response = await fetch(`${API_BASE}/export/ropa?${params}`)
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Exportieren des RoPA')
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
return url
|
||||
}
|
||||
334
admin-lehrer/lib/sdk/vendor-compliance/context-reducer.ts
Normal file
334
admin-lehrer/lib/sdk/vendor-compliance/context-reducer.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
import type {
|
||||
VendorComplianceState,
|
||||
VendorComplianceAction,
|
||||
VendorStatistics,
|
||||
ComplianceStatistics,
|
||||
RiskOverview,
|
||||
VendorStatus,
|
||||
VendorRole,
|
||||
RiskLevel,
|
||||
FindingType,
|
||||
FindingSeverity,
|
||||
} from './types'
|
||||
|
||||
import { getRiskLevelFromScore } from './types'
|
||||
|
||||
// ==========================================
|
||||
// INITIAL STATE
|
||||
// ==========================================
|
||||
|
||||
export const initialState: VendorComplianceState = {
|
||||
processingActivities: [],
|
||||
vendors: [],
|
||||
contracts: [],
|
||||
findings: [],
|
||||
controls: [],
|
||||
controlInstances: [],
|
||||
riskAssessments: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
selectedVendorId: null,
|
||||
selectedActivityId: null,
|
||||
activeTab: 'overview',
|
||||
lastModified: null,
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// REDUCER
|
||||
// ==========================================
|
||||
|
||||
export function vendorComplianceReducer(
|
||||
state: VendorComplianceState,
|
||||
action: VendorComplianceAction
|
||||
): VendorComplianceState {
|
||||
const updateState = (updates: Partial<VendorComplianceState>): VendorComplianceState => ({
|
||||
...state,
|
||||
...updates,
|
||||
lastModified: new Date(),
|
||||
})
|
||||
|
||||
switch (action.type) {
|
||||
// Processing Activities
|
||||
case 'SET_PROCESSING_ACTIVITIES':
|
||||
return updateState({ processingActivities: action.payload })
|
||||
|
||||
case 'ADD_PROCESSING_ACTIVITY':
|
||||
return updateState({
|
||||
processingActivities: [...state.processingActivities, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_PROCESSING_ACTIVITY':
|
||||
return updateState({
|
||||
processingActivities: state.processingActivities.map((activity) =>
|
||||
activity.id === action.payload.id
|
||||
? { ...activity, ...action.payload.data, updatedAt: new Date() }
|
||||
: activity
|
||||
),
|
||||
})
|
||||
|
||||
case 'DELETE_PROCESSING_ACTIVITY':
|
||||
return updateState({
|
||||
processingActivities: state.processingActivities.filter(
|
||||
(activity) => activity.id !== action.payload
|
||||
),
|
||||
})
|
||||
|
||||
// Vendors
|
||||
case 'SET_VENDORS':
|
||||
return updateState({ vendors: action.payload })
|
||||
|
||||
case 'ADD_VENDOR':
|
||||
return updateState({
|
||||
vendors: [...state.vendors, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_VENDOR':
|
||||
return updateState({
|
||||
vendors: state.vendors.map((vendor) =>
|
||||
vendor.id === action.payload.id
|
||||
? { ...vendor, ...action.payload.data, updatedAt: new Date() }
|
||||
: vendor
|
||||
),
|
||||
})
|
||||
|
||||
case 'DELETE_VENDOR':
|
||||
return updateState({
|
||||
vendors: state.vendors.filter((vendor) => vendor.id !== action.payload),
|
||||
})
|
||||
|
||||
// Contracts
|
||||
case 'SET_CONTRACTS':
|
||||
return updateState({ contracts: action.payload })
|
||||
|
||||
case 'ADD_CONTRACT':
|
||||
return updateState({
|
||||
contracts: [...state.contracts, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_CONTRACT':
|
||||
return updateState({
|
||||
contracts: state.contracts.map((contract) =>
|
||||
contract.id === action.payload.id
|
||||
? { ...contract, ...action.payload.data, updatedAt: new Date() }
|
||||
: contract
|
||||
),
|
||||
})
|
||||
|
||||
case 'DELETE_CONTRACT':
|
||||
return updateState({
|
||||
contracts: state.contracts.filter((contract) => contract.id !== action.payload),
|
||||
})
|
||||
|
||||
// Findings
|
||||
case 'SET_FINDINGS':
|
||||
return updateState({ findings: action.payload })
|
||||
|
||||
case 'ADD_FINDINGS':
|
||||
return updateState({
|
||||
findings: [...state.findings, ...action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_FINDING':
|
||||
return updateState({
|
||||
findings: state.findings.map((finding) =>
|
||||
finding.id === action.payload.id
|
||||
? { ...finding, ...action.payload.data, updatedAt: new Date() }
|
||||
: finding
|
||||
),
|
||||
})
|
||||
|
||||
// Controls
|
||||
case 'SET_CONTROLS':
|
||||
return updateState({ controls: action.payload })
|
||||
|
||||
case 'SET_CONTROL_INSTANCES':
|
||||
return updateState({ controlInstances: action.payload })
|
||||
|
||||
case 'UPDATE_CONTROL_INSTANCE':
|
||||
return updateState({
|
||||
controlInstances: state.controlInstances.map((instance) =>
|
||||
instance.id === action.payload.id
|
||||
? { ...instance, ...action.payload.data }
|
||||
: instance
|
||||
),
|
||||
})
|
||||
|
||||
// Risk Assessments
|
||||
case 'SET_RISK_ASSESSMENTS':
|
||||
return updateState({ riskAssessments: action.payload })
|
||||
|
||||
case 'UPDATE_RISK_ASSESSMENT':
|
||||
return updateState({
|
||||
riskAssessments: state.riskAssessments.map((assessment) =>
|
||||
assessment.id === action.payload.id
|
||||
? { ...assessment, ...action.payload.data }
|
||||
: assessment
|
||||
),
|
||||
})
|
||||
|
||||
// UI State
|
||||
case 'SET_LOADING':
|
||||
return { ...state, isLoading: action.payload }
|
||||
|
||||
case 'SET_ERROR':
|
||||
return { ...state, error: action.payload }
|
||||
|
||||
case 'SET_SELECTED_VENDOR':
|
||||
return { ...state, selectedVendorId: action.payload }
|
||||
|
||||
case 'SET_SELECTED_ACTIVITY':
|
||||
return { ...state, selectedActivityId: action.payload }
|
||||
|
||||
case 'SET_ACTIVE_TAB':
|
||||
return { ...state, activeTab: action.payload }
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// COMPUTED VALUES (pure functions of state)
|
||||
// ==========================================
|
||||
|
||||
export function computeVendorStats(state: VendorComplianceState): VendorStatistics {
|
||||
const vendors = state.vendors
|
||||
|
||||
const byStatus = vendors.reduce(
|
||||
(acc, v) => {
|
||||
acc[v.status] = (acc[v.status] || 0) + 1
|
||||
return acc
|
||||
},
|
||||
{} as Record<VendorStatus, number>
|
||||
)
|
||||
|
||||
const byRole = vendors.reduce(
|
||||
(acc, v) => {
|
||||
acc[v.role] = (acc[v.role] || 0) + 1
|
||||
return acc
|
||||
},
|
||||
{} as Record<VendorRole, number>
|
||||
)
|
||||
|
||||
const byRiskLevel = vendors.reduce(
|
||||
(acc, v) => {
|
||||
const level = getRiskLevelFromScore(v.residualRiskScore / 4) // Normalize to 1-25
|
||||
acc[level] = (acc[level] || 0) + 1
|
||||
return acc
|
||||
},
|
||||
{} as Record<RiskLevel, number>
|
||||
)
|
||||
|
||||
const now = new Date()
|
||||
const pendingReviews = vendors.filter(
|
||||
(v) => v.nextReviewDate && new Date(v.nextReviewDate) <= now
|
||||
).length
|
||||
|
||||
const withExpiredContracts = vendors.filter((v) =>
|
||||
state.contracts.some(
|
||||
(c) =>
|
||||
c.vendorId === v.id &&
|
||||
c.expirationDate &&
|
||||
new Date(c.expirationDate) <= now &&
|
||||
c.status === 'ACTIVE'
|
||||
)
|
||||
).length
|
||||
|
||||
return {
|
||||
total: vendors.length,
|
||||
byStatus,
|
||||
byRole,
|
||||
byRiskLevel,
|
||||
pendingReviews,
|
||||
withExpiredContracts,
|
||||
}
|
||||
}
|
||||
|
||||
export function computeComplianceStats(state: VendorComplianceState): ComplianceStatistics {
|
||||
const findings = state.findings
|
||||
const contracts = state.contracts
|
||||
const controlInstances = state.controlInstances
|
||||
|
||||
const averageComplianceScore =
|
||||
contracts.length > 0
|
||||
? contracts.reduce((sum, c) => sum + (c.complianceScore || 0), 0) /
|
||||
contracts.filter((c) => c.complianceScore !== undefined).length || 0
|
||||
: 0
|
||||
|
||||
const findingsByType = findings.reduce(
|
||||
(acc, f) => {
|
||||
acc[f.type] = (acc[f.type] || 0) + 1
|
||||
return acc
|
||||
},
|
||||
{} as Record<FindingType, number>
|
||||
)
|
||||
|
||||
const findingsBySeverity = findings.reduce(
|
||||
(acc, f) => {
|
||||
acc[f.severity] = (acc[f.severity] || 0) + 1
|
||||
return acc
|
||||
},
|
||||
{} as Record<FindingSeverity, number>
|
||||
)
|
||||
|
||||
const openFindings = findings.filter(
|
||||
(f) => f.status === 'OPEN' || f.status === 'IN_PROGRESS'
|
||||
).length
|
||||
|
||||
const resolvedFindings = findings.filter(
|
||||
(f) => f.status === 'RESOLVED' || f.status === 'FALSE_POSITIVE'
|
||||
).length
|
||||
|
||||
const passedControls = controlInstances.filter(
|
||||
(ci) => ci.status === 'PASS'
|
||||
).length
|
||||
const applicableControls = controlInstances.filter(
|
||||
(ci) => ci.status !== 'NOT_APPLICABLE'
|
||||
).length
|
||||
const controlPassRate =
|
||||
applicableControls > 0 ? (passedControls / applicableControls) * 100 : 0
|
||||
|
||||
return {
|
||||
averageComplianceScore,
|
||||
findingsByType,
|
||||
findingsBySeverity,
|
||||
openFindings,
|
||||
resolvedFindings,
|
||||
controlPassRate,
|
||||
}
|
||||
}
|
||||
|
||||
export function computeRiskOverview(state: VendorComplianceState): RiskOverview {
|
||||
const vendors = state.vendors
|
||||
const findings = state.findings
|
||||
|
||||
const averageInherentRisk =
|
||||
vendors.length > 0
|
||||
? vendors.reduce((sum, v) => sum + v.inherentRiskScore, 0) / vendors.length
|
||||
: 0
|
||||
|
||||
const averageResidualRisk =
|
||||
vendors.length > 0
|
||||
? vendors.reduce((sum, v) => sum + v.residualRiskScore, 0) / vendors.length
|
||||
: 0
|
||||
|
||||
const highRiskVendors = vendors.filter(
|
||||
(v) => v.residualRiskScore >= 60
|
||||
).length
|
||||
|
||||
const criticalFindings = findings.filter(
|
||||
(f) => f.severity === 'CRITICAL' && f.status === 'OPEN'
|
||||
).length
|
||||
|
||||
const transfersToThirdCountries = vendors.filter((v) =>
|
||||
v.processingLocations.some((pl) => !pl.isEU && !pl.isAdequate)
|
||||
).length
|
||||
|
||||
return {
|
||||
averageInherentRisk,
|
||||
averageResidualRisk,
|
||||
highRiskVendors,
|
||||
criticalFindings,
|
||||
transfersToThirdCountries,
|
||||
}
|
||||
}
|
||||
@@ -10,202 +10,44 @@ import React, {
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
import {
|
||||
VendorComplianceState,
|
||||
VendorComplianceAction,
|
||||
import type {
|
||||
VendorComplianceContextValue,
|
||||
ProcessingActivity,
|
||||
Vendor,
|
||||
ContractDocument,
|
||||
Finding,
|
||||
Control,
|
||||
ControlInstance,
|
||||
RiskAssessment,
|
||||
VendorStatistics,
|
||||
ComplianceStatistics,
|
||||
RiskOverview,
|
||||
ExportFormat,
|
||||
VendorStatus,
|
||||
VendorRole,
|
||||
RiskLevel,
|
||||
FindingType,
|
||||
FindingSeverity,
|
||||
getRiskLevelFromScore,
|
||||
} from './types'
|
||||
|
||||
// ==========================================
|
||||
// INITIAL STATE
|
||||
// ==========================================
|
||||
import {
|
||||
vendorComplianceReducer,
|
||||
initialState,
|
||||
computeVendorStats,
|
||||
computeComplianceStats,
|
||||
computeRiskOverview,
|
||||
} from './context-reducer'
|
||||
|
||||
const initialState: VendorComplianceState = {
|
||||
processingActivities: [],
|
||||
vendors: [],
|
||||
contracts: [],
|
||||
findings: [],
|
||||
controls: [],
|
||||
controlInstances: [],
|
||||
riskAssessments: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
selectedVendorId: null,
|
||||
selectedActivityId: null,
|
||||
activeTab: 'overview',
|
||||
lastModified: null,
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// REDUCER
|
||||
// ==========================================
|
||||
|
||||
function vendorComplianceReducer(
|
||||
state: VendorComplianceState,
|
||||
action: VendorComplianceAction
|
||||
): VendorComplianceState {
|
||||
const updateState = (updates: Partial<VendorComplianceState>): VendorComplianceState => ({
|
||||
...state,
|
||||
...updates,
|
||||
lastModified: new Date(),
|
||||
})
|
||||
|
||||
switch (action.type) {
|
||||
// Processing Activities
|
||||
case 'SET_PROCESSING_ACTIVITIES':
|
||||
return updateState({ processingActivities: action.payload })
|
||||
|
||||
case 'ADD_PROCESSING_ACTIVITY':
|
||||
return updateState({
|
||||
processingActivities: [...state.processingActivities, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_PROCESSING_ACTIVITY':
|
||||
return updateState({
|
||||
processingActivities: state.processingActivities.map((activity) =>
|
||||
activity.id === action.payload.id
|
||||
? { ...activity, ...action.payload.data, updatedAt: new Date() }
|
||||
: activity
|
||||
),
|
||||
})
|
||||
|
||||
case 'DELETE_PROCESSING_ACTIVITY':
|
||||
return updateState({
|
||||
processingActivities: state.processingActivities.filter(
|
||||
(activity) => activity.id !== action.payload
|
||||
),
|
||||
})
|
||||
|
||||
// Vendors
|
||||
case 'SET_VENDORS':
|
||||
return updateState({ vendors: action.payload })
|
||||
|
||||
case 'ADD_VENDOR':
|
||||
return updateState({
|
||||
vendors: [...state.vendors, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_VENDOR':
|
||||
return updateState({
|
||||
vendors: state.vendors.map((vendor) =>
|
||||
vendor.id === action.payload.id
|
||||
? { ...vendor, ...action.payload.data, updatedAt: new Date() }
|
||||
: vendor
|
||||
),
|
||||
})
|
||||
|
||||
case 'DELETE_VENDOR':
|
||||
return updateState({
|
||||
vendors: state.vendors.filter((vendor) => vendor.id !== action.payload),
|
||||
})
|
||||
|
||||
// Contracts
|
||||
case 'SET_CONTRACTS':
|
||||
return updateState({ contracts: action.payload })
|
||||
|
||||
case 'ADD_CONTRACT':
|
||||
return updateState({
|
||||
contracts: [...state.contracts, action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_CONTRACT':
|
||||
return updateState({
|
||||
contracts: state.contracts.map((contract) =>
|
||||
contract.id === action.payload.id
|
||||
? { ...contract, ...action.payload.data, updatedAt: new Date() }
|
||||
: contract
|
||||
),
|
||||
})
|
||||
|
||||
case 'DELETE_CONTRACT':
|
||||
return updateState({
|
||||
contracts: state.contracts.filter((contract) => contract.id !== action.payload),
|
||||
})
|
||||
|
||||
// Findings
|
||||
case 'SET_FINDINGS':
|
||||
return updateState({ findings: action.payload })
|
||||
|
||||
case 'ADD_FINDINGS':
|
||||
return updateState({
|
||||
findings: [...state.findings, ...action.payload],
|
||||
})
|
||||
|
||||
case 'UPDATE_FINDING':
|
||||
return updateState({
|
||||
findings: state.findings.map((finding) =>
|
||||
finding.id === action.payload.id
|
||||
? { ...finding, ...action.payload.data, updatedAt: new Date() }
|
||||
: finding
|
||||
),
|
||||
})
|
||||
|
||||
// Controls
|
||||
case 'SET_CONTROLS':
|
||||
return updateState({ controls: action.payload })
|
||||
|
||||
case 'SET_CONTROL_INSTANCES':
|
||||
return updateState({ controlInstances: action.payload })
|
||||
|
||||
case 'UPDATE_CONTROL_INSTANCE':
|
||||
return updateState({
|
||||
controlInstances: state.controlInstances.map((instance) =>
|
||||
instance.id === action.payload.id
|
||||
? { ...instance, ...action.payload.data }
|
||||
: instance
|
||||
),
|
||||
})
|
||||
|
||||
// Risk Assessments
|
||||
case 'SET_RISK_ASSESSMENTS':
|
||||
return updateState({ riskAssessments: action.payload })
|
||||
|
||||
case 'UPDATE_RISK_ASSESSMENT':
|
||||
return updateState({
|
||||
riskAssessments: state.riskAssessments.map((assessment) =>
|
||||
assessment.id === action.payload.id
|
||||
? { ...assessment, ...action.payload.data }
|
||||
: assessment
|
||||
),
|
||||
})
|
||||
|
||||
// UI State
|
||||
case 'SET_LOADING':
|
||||
return { ...state, isLoading: action.payload }
|
||||
|
||||
case 'SET_ERROR':
|
||||
return { ...state, error: action.payload }
|
||||
|
||||
case 'SET_SELECTED_VENDOR':
|
||||
return { ...state, selectedVendorId: action.payload }
|
||||
|
||||
case 'SET_SELECTED_ACTIVITY':
|
||||
return { ...state, selectedActivityId: action.payload }
|
||||
|
||||
case 'SET_ACTIVE_TAB':
|
||||
return { ...state, activeTab: action.payload }
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
import {
|
||||
loadAllData,
|
||||
apiCreateProcessingActivity,
|
||||
apiUpdateProcessingActivity,
|
||||
apiDeleteProcessingActivity,
|
||||
apiDuplicateProcessingActivity,
|
||||
apiCreateVendor,
|
||||
apiUpdateVendor,
|
||||
apiDeleteVendor,
|
||||
apiUploadContract,
|
||||
apiUpdateContract,
|
||||
apiDeleteContract,
|
||||
apiStartContractReview,
|
||||
apiUpdateFinding,
|
||||
apiResolveFinding,
|
||||
apiUpdateControlInstance,
|
||||
apiExportVVT,
|
||||
apiExportVendorAuditPack,
|
||||
apiExportRoPA,
|
||||
} from './context-actions'
|
||||
|
||||
// ==========================================
|
||||
// CONTEXT
|
||||
@@ -233,626 +75,150 @@ export function VendorComplianceProvider({
|
||||
// COMPUTED VALUES
|
||||
// ==========================================
|
||||
|
||||
const vendorStats = useMemo<VendorStatistics>(() => {
|
||||
const vendors = state.vendors
|
||||
const vendorStats = useMemo(
|
||||
() => computeVendorStats(state),
|
||||
[state.vendors, state.contracts]
|
||||
)
|
||||
|
||||
const byStatus = vendors.reduce(
|
||||
(acc, v) => {
|
||||
acc[v.status] = (acc[v.status] || 0) + 1
|
||||
return acc
|
||||
},
|
||||
{} as Record<VendorStatus, number>
|
||||
)
|
||||
const complianceStats = useMemo(
|
||||
() => computeComplianceStats(state),
|
||||
[state.findings, state.contracts, state.controlInstances]
|
||||
)
|
||||
|
||||
const byRole = vendors.reduce(
|
||||
(acc, v) => {
|
||||
acc[v.role] = (acc[v.role] || 0) + 1
|
||||
return acc
|
||||
},
|
||||
{} as Record<VendorRole, number>
|
||||
)
|
||||
|
||||
const byRiskLevel = vendors.reduce(
|
||||
(acc, v) => {
|
||||
const level = getRiskLevelFromScore(v.residualRiskScore / 4) // Normalize to 1-25
|
||||
acc[level] = (acc[level] || 0) + 1
|
||||
return acc
|
||||
},
|
||||
{} as Record<RiskLevel, number>
|
||||
)
|
||||
|
||||
const now = new Date()
|
||||
const pendingReviews = vendors.filter(
|
||||
(v) => v.nextReviewDate && new Date(v.nextReviewDate) <= now
|
||||
).length
|
||||
|
||||
const withExpiredContracts = vendors.filter((v) =>
|
||||
state.contracts.some(
|
||||
(c) =>
|
||||
c.vendorId === v.id &&
|
||||
c.expirationDate &&
|
||||
new Date(c.expirationDate) <= now &&
|
||||
c.status === 'ACTIVE'
|
||||
)
|
||||
).length
|
||||
|
||||
return {
|
||||
total: vendors.length,
|
||||
byStatus,
|
||||
byRole,
|
||||
byRiskLevel,
|
||||
pendingReviews,
|
||||
withExpiredContracts,
|
||||
}
|
||||
}, [state.vendors, state.contracts])
|
||||
|
||||
const complianceStats = useMemo<ComplianceStatistics>(() => {
|
||||
const findings = state.findings
|
||||
const contracts = state.contracts
|
||||
const controlInstances = state.controlInstances
|
||||
|
||||
const averageComplianceScore =
|
||||
contracts.length > 0
|
||||
? contracts.reduce((sum, c) => sum + (c.complianceScore || 0), 0) /
|
||||
contracts.filter((c) => c.complianceScore !== undefined).length || 0
|
||||
: 0
|
||||
|
||||
const findingsByType = findings.reduce(
|
||||
(acc, f) => {
|
||||
acc[f.type] = (acc[f.type] || 0) + 1
|
||||
return acc
|
||||
},
|
||||
{} as Record<FindingType, number>
|
||||
)
|
||||
|
||||
const findingsBySeverity = findings.reduce(
|
||||
(acc, f) => {
|
||||
acc[f.severity] = (acc[f.severity] || 0) + 1
|
||||
return acc
|
||||
},
|
||||
{} as Record<FindingSeverity, number>
|
||||
)
|
||||
|
||||
const openFindings = findings.filter(
|
||||
(f) => f.status === 'OPEN' || f.status === 'IN_PROGRESS'
|
||||
).length
|
||||
|
||||
const resolvedFindings = findings.filter(
|
||||
(f) => f.status === 'RESOLVED' || f.status === 'FALSE_POSITIVE'
|
||||
).length
|
||||
|
||||
const passedControls = controlInstances.filter(
|
||||
(ci) => ci.status === 'PASS'
|
||||
).length
|
||||
const applicableControls = controlInstances.filter(
|
||||
(ci) => ci.status !== 'NOT_APPLICABLE'
|
||||
).length
|
||||
const controlPassRate =
|
||||
applicableControls > 0 ? (passedControls / applicableControls) * 100 : 0
|
||||
|
||||
return {
|
||||
averageComplianceScore,
|
||||
findingsByType,
|
||||
findingsBySeverity,
|
||||
openFindings,
|
||||
resolvedFindings,
|
||||
controlPassRate,
|
||||
}
|
||||
}, [state.findings, state.contracts, state.controlInstances])
|
||||
|
||||
const riskOverview = useMemo<RiskOverview>(() => {
|
||||
const vendors = state.vendors
|
||||
const findings = state.findings
|
||||
|
||||
const averageInherentRisk =
|
||||
vendors.length > 0
|
||||
? vendors.reduce((sum, v) => sum + v.inherentRiskScore, 0) / vendors.length
|
||||
: 0
|
||||
|
||||
const averageResidualRisk =
|
||||
vendors.length > 0
|
||||
? vendors.reduce((sum, v) => sum + v.residualRiskScore, 0) / vendors.length
|
||||
: 0
|
||||
|
||||
const highRiskVendors = vendors.filter(
|
||||
(v) => v.residualRiskScore >= 60
|
||||
).length
|
||||
|
||||
const criticalFindings = findings.filter(
|
||||
(f) => f.severity === 'CRITICAL' && f.status === 'OPEN'
|
||||
).length
|
||||
|
||||
const transfersToThirdCountries = vendors.filter((v) =>
|
||||
v.processingLocations.some((pl) => !pl.isEU && !pl.isAdequate)
|
||||
).length
|
||||
|
||||
return {
|
||||
averageInherentRisk,
|
||||
averageResidualRisk,
|
||||
highRiskVendors,
|
||||
criticalFindings,
|
||||
transfersToThirdCountries,
|
||||
}
|
||||
}, [state.vendors, state.findings])
|
||||
const riskOverview = useMemo(
|
||||
() => computeRiskOverview(state),
|
||||
[state.vendors, state.findings]
|
||||
)
|
||||
|
||||
// ==========================================
|
||||
// API CALLS
|
||||
// ACTION WRAPPERS
|
||||
// ==========================================
|
||||
|
||||
const apiBase = '/api/sdk/v1/vendor-compliance'
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
dispatch({ type: 'SET_LOADING', payload: true })
|
||||
dispatch({ type: 'SET_ERROR', payload: null })
|
||||
|
||||
try {
|
||||
const [
|
||||
activitiesRes,
|
||||
vendorsRes,
|
||||
contractsRes,
|
||||
findingsRes,
|
||||
controlsRes,
|
||||
controlInstancesRes,
|
||||
] = await Promise.all([
|
||||
fetch(`${apiBase}/processing-activities`),
|
||||
fetch(`${apiBase}/vendors`),
|
||||
fetch(`${apiBase}/contracts`),
|
||||
fetch(`${apiBase}/findings`),
|
||||
fetch(`${apiBase}/controls`),
|
||||
fetch(`${apiBase}/control-instances`),
|
||||
])
|
||||
|
||||
if (activitiesRes.ok) {
|
||||
const data = await activitiesRes.json()
|
||||
dispatch({ type: 'SET_PROCESSING_ACTIVITIES', payload: data.data || [] })
|
||||
}
|
||||
|
||||
if (vendorsRes.ok) {
|
||||
const data = await vendorsRes.json()
|
||||
dispatch({ type: 'SET_VENDORS', payload: data.data || [] })
|
||||
}
|
||||
|
||||
if (contractsRes.ok) {
|
||||
const data = await contractsRes.json()
|
||||
dispatch({ type: 'SET_CONTRACTS', payload: data.data || [] })
|
||||
}
|
||||
|
||||
if (findingsRes.ok) {
|
||||
const data = await findingsRes.json()
|
||||
dispatch({ type: 'SET_FINDINGS', payload: data.data || [] })
|
||||
}
|
||||
|
||||
if (controlsRes.ok) {
|
||||
const data = await controlsRes.json()
|
||||
dispatch({ type: 'SET_CONTROLS', payload: data.data || [] })
|
||||
}
|
||||
|
||||
if (controlInstancesRes.ok) {
|
||||
const data = await controlInstancesRes.json()
|
||||
dispatch({ type: 'SET_CONTROL_INSTANCES', payload: data.data || [] })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load vendor compliance data:', error)
|
||||
dispatch({
|
||||
type: 'SET_ERROR',
|
||||
payload: 'Fehler beim Laden der Daten',
|
||||
})
|
||||
} finally {
|
||||
dispatch({ type: 'SET_LOADING', payload: false })
|
||||
}
|
||||
}, [apiBase])
|
||||
await loadAllData(dispatch)
|
||||
}, [])
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
await loadData()
|
||||
}, [loadData])
|
||||
|
||||
// ==========================================
|
||||
// PROCESSING ACTIVITIES ACTIONS
|
||||
// ==========================================
|
||||
|
||||
const createProcessingActivity = useCallback(
|
||||
async (
|
||||
data: Omit<ProcessingActivity, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>
|
||||
): Promise<ProcessingActivity> => {
|
||||
const response = await fetch(`${apiBase}/processing-activities`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Erstellen der Verarbeitungstätigkeit')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const activity = result.data
|
||||
|
||||
dispatch({ type: 'ADD_PROCESSING_ACTIVITY', payload: activity })
|
||||
|
||||
return activity
|
||||
async (data: Omit<ProcessingActivity, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>) => {
|
||||
return apiCreateProcessingActivity(dispatch, data)
|
||||
},
|
||||
[apiBase]
|
||||
[]
|
||||
)
|
||||
|
||||
const updateProcessingActivity = useCallback(
|
||||
async (id: string, data: Partial<ProcessingActivity>): Promise<void> => {
|
||||
const response = await fetch(`${apiBase}/processing-activities/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Aktualisieren der Verarbeitungstätigkeit')
|
||||
}
|
||||
|
||||
dispatch({ type: 'UPDATE_PROCESSING_ACTIVITY', payload: { id, data } })
|
||||
async (id: string, data: Partial<ProcessingActivity>) => {
|
||||
await apiUpdateProcessingActivity(dispatch, id, data)
|
||||
},
|
||||
[apiBase]
|
||||
[]
|
||||
)
|
||||
|
||||
const deleteProcessingActivity = useCallback(
|
||||
async (id: string): Promise<void> => {
|
||||
const response = await fetch(`${apiBase}/processing-activities/${id}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Löschen der Verarbeitungstätigkeit')
|
||||
}
|
||||
|
||||
dispatch({ type: 'DELETE_PROCESSING_ACTIVITY', payload: id })
|
||||
async (id: string) => {
|
||||
await apiDeleteProcessingActivity(dispatch, id)
|
||||
},
|
||||
[apiBase]
|
||||
[]
|
||||
)
|
||||
|
||||
const duplicateProcessingActivity = useCallback(
|
||||
async (id: string): Promise<ProcessingActivity> => {
|
||||
const original = state.processingActivities.find((a) => a.id === id)
|
||||
if (!original) {
|
||||
throw new Error('Verarbeitungstätigkeit nicht gefunden')
|
||||
}
|
||||
|
||||
const { id: _id, vvtId: _vvtId, createdAt: _createdAt, updatedAt: _updatedAt, tenantId: _tenantId, ...rest } = original
|
||||
|
||||
const newActivity = await createProcessingActivity({
|
||||
...rest,
|
||||
vvtId: '', // Will be generated by backend
|
||||
name: {
|
||||
de: `${original.name.de} (Kopie)`,
|
||||
en: `${original.name.en} (Copy)`,
|
||||
},
|
||||
status: 'DRAFT',
|
||||
})
|
||||
|
||||
return newActivity
|
||||
async (id: string) => {
|
||||
return apiDuplicateProcessingActivity(dispatch, state, id)
|
||||
},
|
||||
[state.processingActivities, createProcessingActivity]
|
||||
[state]
|
||||
)
|
||||
|
||||
// ==========================================
|
||||
// VENDOR ACTIONS
|
||||
// ==========================================
|
||||
|
||||
const createVendor = useCallback(
|
||||
async (
|
||||
data: Omit<Vendor, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>
|
||||
): Promise<Vendor> => {
|
||||
const response = await fetch(`${apiBase}/vendors`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Erstellen des Vendors')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const vendor = result.data
|
||||
|
||||
dispatch({ type: 'ADD_VENDOR', payload: vendor })
|
||||
|
||||
return vendor
|
||||
async (data: Omit<Vendor, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>) => {
|
||||
return apiCreateVendor(dispatch, data)
|
||||
},
|
||||
[apiBase]
|
||||
[]
|
||||
)
|
||||
|
||||
const updateVendor = useCallback(
|
||||
async (id: string, data: Partial<Vendor>): Promise<void> => {
|
||||
const response = await fetch(`${apiBase}/vendors/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Aktualisieren des Vendors')
|
||||
}
|
||||
|
||||
dispatch({ type: 'UPDATE_VENDOR', payload: { id, data } })
|
||||
async (id: string, data: Partial<Vendor>) => {
|
||||
await apiUpdateVendor(dispatch, id, data)
|
||||
},
|
||||
[apiBase]
|
||||
[]
|
||||
)
|
||||
|
||||
const deleteVendor = useCallback(
|
||||
async (id: string): Promise<void> => {
|
||||
const response = await fetch(`${apiBase}/vendors/${id}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Löschen des Vendors')
|
||||
}
|
||||
|
||||
dispatch({ type: 'DELETE_VENDOR', payload: id })
|
||||
async (id: string) => {
|
||||
await apiDeleteVendor(dispatch, id)
|
||||
},
|
||||
[apiBase]
|
||||
[]
|
||||
)
|
||||
|
||||
// ==========================================
|
||||
// CONTRACT ACTIONS
|
||||
// ==========================================
|
||||
|
||||
const uploadContract = useCallback(
|
||||
async (
|
||||
vendorId: string,
|
||||
file: File,
|
||||
metadata: Partial<ContractDocument>
|
||||
): Promise<ContractDocument> => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('vendorId', vendorId)
|
||||
formData.append('metadata', JSON.stringify(metadata))
|
||||
|
||||
const response = await fetch(`${apiBase}/contracts`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Hochladen des Vertrags')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const contract = result.data
|
||||
|
||||
dispatch({ type: 'ADD_CONTRACT', payload: contract })
|
||||
|
||||
// Update vendor's contracts list
|
||||
const vendor = state.vendors.find((v) => v.id === vendorId)
|
||||
if (vendor) {
|
||||
dispatch({
|
||||
type: 'UPDATE_VENDOR',
|
||||
payload: {
|
||||
id: vendorId,
|
||||
data: { contracts: [...vendor.contracts, contract.id] },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return contract
|
||||
async (vendorId: string, file: File, metadata: Partial<ContractDocument>) => {
|
||||
return apiUploadContract(dispatch, state, vendorId, file, metadata)
|
||||
},
|
||||
[apiBase, state.vendors]
|
||||
[state]
|
||||
)
|
||||
|
||||
const updateContract = useCallback(
|
||||
async (id: string, data: Partial<ContractDocument>): Promise<void> => {
|
||||
const response = await fetch(`${apiBase}/contracts/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Aktualisieren des Vertrags')
|
||||
}
|
||||
|
||||
dispatch({ type: 'UPDATE_CONTRACT', payload: { id, data } })
|
||||
async (id: string, data: Partial<ContractDocument>) => {
|
||||
await apiUpdateContract(dispatch, id, data)
|
||||
},
|
||||
[apiBase]
|
||||
[]
|
||||
)
|
||||
|
||||
const deleteContract = useCallback(
|
||||
async (id: string): Promise<void> => {
|
||||
const contract = state.contracts.find((c) => c.id === id)
|
||||
|
||||
const response = await fetch(`${apiBase}/contracts/${id}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Löschen des Vertrags')
|
||||
}
|
||||
|
||||
dispatch({ type: 'DELETE_CONTRACT', payload: id })
|
||||
|
||||
// Update vendor's contracts list
|
||||
if (contract) {
|
||||
const vendor = state.vendors.find((v) => v.id === contract.vendorId)
|
||||
if (vendor) {
|
||||
dispatch({
|
||||
type: 'UPDATE_VENDOR',
|
||||
payload: {
|
||||
id: vendor.id,
|
||||
data: { contracts: vendor.contracts.filter((cId) => cId !== id) },
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
async (id: string) => {
|
||||
await apiDeleteContract(dispatch, state, id)
|
||||
},
|
||||
[apiBase, state.contracts, state.vendors]
|
||||
[state]
|
||||
)
|
||||
|
||||
const startContractReview = useCallback(
|
||||
async (contractId: string): Promise<void> => {
|
||||
dispatch({
|
||||
type: 'UPDATE_CONTRACT',
|
||||
payload: { id: contractId, data: { reviewStatus: 'IN_PROGRESS' } },
|
||||
})
|
||||
|
||||
const response = await fetch(`${apiBase}/contracts/${contractId}/review`, {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
dispatch({
|
||||
type: 'UPDATE_CONTRACT',
|
||||
payload: { id: contractId, data: { reviewStatus: 'FAILED' } },
|
||||
})
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Starten der Vertragsprüfung')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
// Update contract with review results
|
||||
dispatch({
|
||||
type: 'UPDATE_CONTRACT',
|
||||
payload: {
|
||||
id: contractId,
|
||||
data: {
|
||||
reviewStatus: 'COMPLETED',
|
||||
reviewCompletedAt: new Date(),
|
||||
complianceScore: result.data.complianceScore,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Add findings
|
||||
if (result.data.findings && result.data.findings.length > 0) {
|
||||
dispatch({ type: 'ADD_FINDINGS', payload: result.data.findings })
|
||||
}
|
||||
async (contractId: string) => {
|
||||
await apiStartContractReview(dispatch, contractId)
|
||||
},
|
||||
[apiBase]
|
||||
[]
|
||||
)
|
||||
|
||||
// ==========================================
|
||||
// FINDINGS ACTIONS
|
||||
// ==========================================
|
||||
|
||||
const updateFinding = useCallback(
|
||||
async (id: string, data: Partial<Finding>): Promise<void> => {
|
||||
const response = await fetch(`${apiBase}/findings/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Aktualisieren des Findings')
|
||||
}
|
||||
|
||||
dispatch({ type: 'UPDATE_FINDING', payload: { id, data } })
|
||||
async (id: string, data: Partial<Finding>) => {
|
||||
await apiUpdateFinding(dispatch, id, data)
|
||||
},
|
||||
[apiBase]
|
||||
[]
|
||||
)
|
||||
|
||||
const resolveFinding = useCallback(
|
||||
async (id: string, resolution: string): Promise<void> => {
|
||||
await updateFinding(id, {
|
||||
status: 'RESOLVED',
|
||||
resolution,
|
||||
resolvedAt: new Date(),
|
||||
})
|
||||
async (id: string, resolution: string) => {
|
||||
await apiResolveFinding(dispatch, id, resolution)
|
||||
},
|
||||
[updateFinding]
|
||||
[]
|
||||
)
|
||||
|
||||
// ==========================================
|
||||
// CONTROL ACTIONS
|
||||
// ==========================================
|
||||
|
||||
const updateControlInstance = useCallback(
|
||||
async (id: string, data: Partial<ControlInstance>): Promise<void> => {
|
||||
const response = await fetch(`${apiBase}/control-instances/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Aktualisieren des Control-Status')
|
||||
}
|
||||
|
||||
dispatch({ type: 'UPDATE_CONTROL_INSTANCE', payload: { id, data } })
|
||||
async (id: string, data: Partial<ControlInstance>) => {
|
||||
await apiUpdateControlInstance(dispatch, id, data)
|
||||
},
|
||||
[apiBase]
|
||||
[]
|
||||
)
|
||||
|
||||
// ==========================================
|
||||
// EXPORT ACTIONS
|
||||
// ==========================================
|
||||
|
||||
const exportVVT = useCallback(
|
||||
async (format: ExportFormat, activityIds?: string[]): Promise<string> => {
|
||||
const params = new URLSearchParams({ format })
|
||||
if (activityIds && activityIds.length > 0) {
|
||||
params.append('activityIds', activityIds.join(','))
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiBase}/export/vvt?${params}`)
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Exportieren des VVT')
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
return url
|
||||
async (format: ExportFormat, activityIds?: string[]) => {
|
||||
return apiExportVVT(format, activityIds)
|
||||
},
|
||||
[apiBase]
|
||||
[]
|
||||
)
|
||||
|
||||
const exportVendorAuditPack = useCallback(
|
||||
async (vendorId: string, format: ExportFormat): Promise<string> => {
|
||||
const params = new URLSearchParams({ format, vendorId })
|
||||
|
||||
const response = await fetch(`${apiBase}/export/vendor-audit?${params}`)
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Exportieren des Vendor Audit Packs')
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
return url
|
||||
async (vendorId: string, format: ExportFormat) => {
|
||||
return apiExportVendorAuditPack(vendorId, format)
|
||||
},
|
||||
[apiBase]
|
||||
[]
|
||||
)
|
||||
|
||||
const exportRoPA = useCallback(
|
||||
async (format: ExportFormat): Promise<string> => {
|
||||
const params = new URLSearchParams({ format })
|
||||
|
||||
const response = await fetch(`${apiBase}/export/ropa?${params}`)
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error || 'Fehler beim Exportieren des RoPA')
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
return url
|
||||
async (format: ExportFormat) => {
|
||||
return apiExportRoPA(format)
|
||||
},
|
||||
[apiBase]
|
||||
[]
|
||||
)
|
||||
|
||||
// ==========================================
|
||||
|
||||
37
admin-lehrer/lib/sdk/vendor-compliance/risk/controls-all.ts
Normal file
37
admin-lehrer/lib/sdk/vendor-compliance/risk/controls-all.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Controls Library - Merged Array
|
||||
*
|
||||
* Assembles all domain-specific control arrays into the single CONTROLS_LIBRARY.
|
||||
* This file exists to avoid circular imports between the barrel and helpers.
|
||||
*/
|
||||
|
||||
import { Control } from '../types'
|
||||
|
||||
import {
|
||||
TRANSFER_CONTROLS,
|
||||
AUDIT_CONTROLS,
|
||||
DELETION_CONTROLS,
|
||||
INCIDENT_CONTROLS,
|
||||
} from './controls-data-compliance'
|
||||
|
||||
import {
|
||||
SUBPROCESSOR_CONTROLS,
|
||||
TOM_CONTROLS,
|
||||
CONTRACT_CONTROLS,
|
||||
DATA_SUBJECT_CONTROLS,
|
||||
SECURITY_CONTROLS,
|
||||
GOVERNANCE_CONTROLS,
|
||||
} from './controls-data-operations'
|
||||
|
||||
export const CONTROLS_LIBRARY: Control[] = [
|
||||
...TRANSFER_CONTROLS,
|
||||
...AUDIT_CONTROLS,
|
||||
...DELETION_CONTROLS,
|
||||
...INCIDENT_CONTROLS,
|
||||
...SUBPROCESSOR_CONTROLS,
|
||||
...TOM_CONTROLS,
|
||||
...CONTRACT_CONTROLS,
|
||||
...DATA_SUBJECT_CONTROLS,
|
||||
...SECURITY_CONTROLS,
|
||||
...GOVERNANCE_CONTROLS,
|
||||
]
|
||||
@@ -0,0 +1,377 @@
|
||||
/**
|
||||
* Control Definitions - Compliance Domains
|
||||
*
|
||||
* Controls for: TRANSFER, AUDIT, DELETION, INCIDENT
|
||||
*/
|
||||
|
||||
import { Control } from '../types'
|
||||
|
||||
// ==========================================
|
||||
// TRANSFER - Drittlandtransfer Controls
|
||||
// ==========================================
|
||||
|
||||
export const TRANSFER_CONTROLS: Control[] = [
|
||||
{
|
||||
id: 'VND-TRF-01',
|
||||
domain: 'TRANSFER',
|
||||
title: {
|
||||
de: 'Drittlandtransfer nur mit Rechtsgrundlage',
|
||||
en: 'Third country transfer with legal basis',
|
||||
},
|
||||
description: {
|
||||
de: 'Drittlandtransfers erfolgen nur auf Basis von SCC, BCR oder Angemessenheitsbeschluss',
|
||||
en: 'Third country transfers only based on SCC, BCR or adequacy decision',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'SCC oder BCR vertraglich vereinbart ODER Angemessenheitsbeschluss vorhanden',
|
||||
en: 'SCC or BCR contractually agreed OR adequacy decision exists',
|
||||
},
|
||||
requirements: ['Art. 44-49 DSGVO', 'ISO 27001 A.15.1.2'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TRF-02',
|
||||
domain: 'TRANSFER',
|
||||
title: {
|
||||
de: 'Aktuelle Standardvertragsklauseln',
|
||||
en: 'Current Standard Contractual Clauses',
|
||||
},
|
||||
description: {
|
||||
de: 'Bei SCC-Nutzung: Verwendung der aktuellen EU-Kommission-Klauseln (2021)',
|
||||
en: 'When using SCC: Current EU Commission clauses (2021) are used',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'SCC 2021 (Durchführungsbeschluss (EU) 2021/914) verwendet',
|
||||
en: 'SCC 2021 (Implementing Decision (EU) 2021/914) used',
|
||||
},
|
||||
requirements: ['Art. 46 Abs. 2 lit. c DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TRF-03',
|
||||
domain: 'TRANSFER',
|
||||
title: {
|
||||
de: 'Transfer Impact Assessment (TIA)',
|
||||
en: 'Transfer Impact Assessment (TIA)',
|
||||
},
|
||||
description: {
|
||||
de: 'Bei Transfers in Drittländer ohne Angemessenheitsbeschluss ist TIA durchzuführen',
|
||||
en: 'TIA required for transfers to third countries without adequacy decision',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'TIA dokumentiert und bewertet Risiken als akzeptabel',
|
||||
en: 'TIA documented and risks assessed as acceptable',
|
||||
},
|
||||
requirements: ['Schrems II Urteil', 'EDSA Empfehlungen 01/2020'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TRF-04',
|
||||
domain: 'TRANSFER',
|
||||
title: {
|
||||
de: 'Zusätzliche Schutzmaßnahmen',
|
||||
en: 'Supplementary Measures',
|
||||
},
|
||||
description: {
|
||||
de: 'Bei Bedarf sind zusätzliche technische/organisatorische Maßnahmen implementiert',
|
||||
en: 'Supplementary technical/organizational measures implemented where needed',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Ergänzende Maßnahmen dokumentiert (Verschlüsselung, Pseudonymisierung, etc.)',
|
||||
en: 'Supplementary measures documented (encryption, pseudonymization, etc.)',
|
||||
},
|
||||
requirements: ['EDSA Empfehlungen 01/2020'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TRF-05',
|
||||
domain: 'TRANSFER',
|
||||
title: {
|
||||
de: 'Überwachung Angemessenheitsbeschlüsse',
|
||||
en: 'Monitoring Adequacy Decisions',
|
||||
},
|
||||
description: {
|
||||
de: 'Änderungen bei Angemessenheitsbeschlüssen werden überwacht',
|
||||
en: 'Changes to adequacy decisions are monitored',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Prozess zur Überwachung und Reaktion auf Änderungen etabliert',
|
||||
en: 'Process for monitoring and responding to changes established',
|
||||
},
|
||||
requirements: ['Art. 45 DSGVO'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'QUARTERLY',
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// AUDIT - Auditrechte Controls
|
||||
// ==========================================
|
||||
|
||||
export const AUDIT_CONTROLS: Control[] = [
|
||||
{
|
||||
id: 'VND-AUD-01',
|
||||
domain: 'AUDIT',
|
||||
title: {
|
||||
de: 'Auditrecht vertraglich vereinbart',
|
||||
en: 'Audit right contractually agreed',
|
||||
},
|
||||
description: {
|
||||
de: 'Vertrag enthält wirksames Auditrecht ohne unangemessene Einschränkungen',
|
||||
en: 'Contract contains effective audit right without unreasonable restrictions',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Auditrecht im AVV enthalten, max. 30 Tage Vorlaufzeit, keine Ausschlussklausel',
|
||||
en: 'Audit right in DPA, max 30 days notice, no exclusion clause',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-AUD-02',
|
||||
domain: 'AUDIT',
|
||||
title: {
|
||||
de: 'Vor-Ort-Inspektionen möglich',
|
||||
en: 'On-site inspections possible',
|
||||
},
|
||||
description: {
|
||||
de: 'Vertrag erlaubt Vor-Ort-Inspektionen bei dem Auftragsverarbeiter',
|
||||
en: 'Contract allows on-site inspections at the processor',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Vor-Ort-Audit explizit erlaubt, Zugang zu relevanten Bereichen',
|
||||
en: 'On-site audit explicitly allowed, access to relevant areas',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-AUD-03',
|
||||
domain: 'AUDIT',
|
||||
title: {
|
||||
de: 'Aktuelle Zertifizierungen',
|
||||
en: 'Current Certifications',
|
||||
},
|
||||
description: {
|
||||
de: 'Relevante Sicherheitszertifizierungen sind aktuell und gültig',
|
||||
en: 'Relevant security certifications are current and valid',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'ISO 27001, SOC 2 oder vergleichbar, nicht abgelaufen',
|
||||
en: 'ISO 27001, SOC 2 or equivalent, not expired',
|
||||
},
|
||||
requirements: ['Art. 32 DSGVO', 'ISO 27001 A.15.1.1'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-AUD-04',
|
||||
domain: 'AUDIT',
|
||||
title: {
|
||||
de: 'Letzte Prüfung durchgeführt',
|
||||
en: 'Last review conducted',
|
||||
},
|
||||
description: {
|
||||
de: 'Vendor wurde innerhalb des Review-Zyklus geprüft',
|
||||
en: 'Vendor was reviewed within the review cycle',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Dokumentierte Prüfung innerhalb des festgelegten Intervalls',
|
||||
en: 'Documented review within the defined interval',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-AUD-05',
|
||||
domain: 'AUDIT',
|
||||
title: {
|
||||
de: 'Prüfberichte verfügbar',
|
||||
en: 'Audit reports available',
|
||||
},
|
||||
description: {
|
||||
de: 'Aktuelle Prüfberichte (SOC 2, Penetrationstest, etc.) liegen vor',
|
||||
en: 'Current audit reports (SOC 2, penetration test, etc.) are available',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Prüfberichte nicht älter als 12 Monate',
|
||||
en: 'Audit reports not older than 12 months',
|
||||
},
|
||||
requirements: ['ISO 27001 A.18.2.1'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// DELETION - Löschung Controls
|
||||
// ==========================================
|
||||
|
||||
export const DELETION_CONTROLS: Control[] = [
|
||||
{
|
||||
id: 'VND-DEL-01',
|
||||
domain: 'DELETION',
|
||||
title: {
|
||||
de: 'Löschung/Rückgabe nach Vertragsende',
|
||||
en: 'Deletion/return after contract end',
|
||||
},
|
||||
description: {
|
||||
de: 'Klare Regelung zur Löschung oder Rückgabe aller Daten nach Vertragsende',
|
||||
en: 'Clear provision for deletion or return of all data after contract end',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Löschfrist max. 30 Tage, Löschbestätigung vorgesehen',
|
||||
en: 'Deletion within max 30 days, deletion confirmation provided',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-DEL-02',
|
||||
domain: 'DELETION',
|
||||
title: {
|
||||
de: 'Löschbestätigung',
|
||||
en: 'Deletion confirmation',
|
||||
},
|
||||
description: {
|
||||
de: 'Schriftliche Bestätigung der vollständigen Datenlöschung',
|
||||
en: 'Written confirmation of complete data deletion',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Löschbestätigung vertraglich vereinbart und einforderbar',
|
||||
en: 'Deletion confirmation contractually agreed and enforceable',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-DEL-03',
|
||||
domain: 'DELETION',
|
||||
title: {
|
||||
de: 'Löschung bei Unterauftragnehmern',
|
||||
en: 'Deletion at sub-processors',
|
||||
},
|
||||
description: {
|
||||
de: 'Löschpflicht erstreckt sich auf alle Unterauftragnehmer',
|
||||
en: 'Deletion obligation extends to all sub-processors',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Weitergabe der Löschpflicht an Unterauftragnehmer vertraglich vereinbart',
|
||||
en: 'Transfer of deletion obligation to sub-processors contractually agreed',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. g, d DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-DEL-04',
|
||||
domain: 'DELETION',
|
||||
title: {
|
||||
de: 'Backup-Löschung',
|
||||
en: 'Backup deletion',
|
||||
},
|
||||
description: {
|
||||
de: 'Daten werden auch aus Backups gelöscht',
|
||||
en: 'Data is also deleted from backups',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Backup-Löschung geregelt, max. Aufbewahrungsfrist für Backups definiert',
|
||||
en: 'Backup deletion regulated, max retention period for backups defined',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// INCIDENT - Incident Response Controls
|
||||
// ==========================================
|
||||
|
||||
export const INCIDENT_CONTROLS: Control[] = [
|
||||
{
|
||||
id: 'VND-INC-01',
|
||||
domain: 'INCIDENT',
|
||||
title: {
|
||||
de: 'Meldepflicht bei Datenpannen',
|
||||
en: 'Data breach notification obligation',
|
||||
},
|
||||
description: {
|
||||
de: 'Unverzügliche Meldung von Datenschutzverletzungen',
|
||||
en: 'Immediate notification of data protection violations',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Meldepflicht vereinbart, Frist max. 24-48h, Mindestinhalte definiert',
|
||||
en: 'Notification obligation agreed, deadline max 24-48h, minimum content defined',
|
||||
},
|
||||
requirements: ['Art. 33 Abs. 2 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-INC-02',
|
||||
domain: 'INCIDENT',
|
||||
title: {
|
||||
de: 'Incident Response Plan',
|
||||
en: 'Incident Response Plan',
|
||||
},
|
||||
description: {
|
||||
de: 'Vendor hat dokumentierten Incident Response Plan',
|
||||
en: 'Vendor has documented incident response plan',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Incident Response Plan liegt vor und wurde getestet',
|
||||
en: 'Incident response plan exists and has been tested',
|
||||
},
|
||||
requirements: ['ISO 27001 A.16.1'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-INC-03',
|
||||
domain: 'INCIDENT',
|
||||
title: {
|
||||
de: 'Kontaktstelle für Incidents',
|
||||
en: 'Contact point for incidents',
|
||||
},
|
||||
description: {
|
||||
de: 'Definierte Kontaktstelle für Datenschutzvorfälle',
|
||||
en: 'Defined contact point for data protection incidents',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Kontaktdaten für Incident-Meldungen bekannt und aktuell',
|
||||
en: 'Contact details for incident reporting known and current',
|
||||
},
|
||||
requirements: ['Art. 33 Abs. 2 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'QUARTERLY',
|
||||
},
|
||||
{
|
||||
id: 'VND-INC-04',
|
||||
domain: 'INCIDENT',
|
||||
title: {
|
||||
de: 'Unterstützung bei Incident-Dokumentation',
|
||||
en: 'Support with incident documentation',
|
||||
},
|
||||
description: {
|
||||
de: 'Vendor unterstützt bei der Dokumentation von Vorfällen',
|
||||
en: 'Vendor supports documentation of incidents',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Unterstützungspflicht bei Dokumentation vertraglich vereinbart',
|
||||
en: 'Support obligation for documentation contractually agreed',
|
||||
},
|
||||
requirements: ['Art. 33 Abs. 5 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,486 @@
|
||||
/**
|
||||
* Control Definitions - Operations Domains
|
||||
*
|
||||
* Controls for: SUBPROCESSOR, TOM, CONTRACT, DATA_SUBJECT, SECURITY, GOVERNANCE
|
||||
*/
|
||||
|
||||
import { Control } from '../types'
|
||||
|
||||
// ==========================================
|
||||
// SUBPROCESSOR - Unterauftragnehmer Controls
|
||||
// ==========================================
|
||||
|
||||
export const SUBPROCESSOR_CONTROLS: Control[] = [
|
||||
{
|
||||
id: 'VND-SUB-01',
|
||||
domain: 'SUBPROCESSOR',
|
||||
title: {
|
||||
de: 'Genehmigungspflicht für Unterauftragnehmer',
|
||||
en: 'Approval requirement for sub-processors',
|
||||
},
|
||||
description: {
|
||||
de: 'Einsatz von Unterauftragnehmern nur mit Genehmigung',
|
||||
en: 'Use of sub-processors only with approval',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Genehmigungserfordernis (spezifisch oder allgemein mit Widerspruchsrecht) vereinbart',
|
||||
en: 'Approval requirement (specific or general with objection right) agreed',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 2, 4 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-SUB-02',
|
||||
domain: 'SUBPROCESSOR',
|
||||
title: {
|
||||
de: 'Aktuelle Unterauftragnehmer-Liste',
|
||||
en: 'Current sub-processor list',
|
||||
},
|
||||
description: {
|
||||
de: 'Vollständige und aktuelle Liste aller Unterauftragnehmer',
|
||||
en: 'Complete and current list of all sub-processors',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Liste liegt vor mit Name, Sitz, Verarbeitungszweck',
|
||||
en: 'List available with name, location, processing purpose',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 2 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'QUARTERLY',
|
||||
},
|
||||
{
|
||||
id: 'VND-SUB-03',
|
||||
domain: 'SUBPROCESSOR',
|
||||
title: {
|
||||
de: 'Informationspflicht bei Änderungen',
|
||||
en: 'Notification obligation for changes',
|
||||
},
|
||||
description: {
|
||||
de: 'Information über neue oder geänderte Unterauftragnehmer',
|
||||
en: 'Information about new or changed sub-processors',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Vorabinformation vereinbart, ausreichende Frist für Widerspruch',
|
||||
en: 'Advance notification agreed, sufficient time for objection',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 2 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-SUB-04',
|
||||
domain: 'SUBPROCESSOR',
|
||||
title: {
|
||||
de: 'Weitergabe der Datenschutzpflichten',
|
||||
en: 'Transfer of data protection obligations',
|
||||
},
|
||||
description: {
|
||||
de: 'Datenschutzpflichten werden an Unterauftragnehmer weitergegeben',
|
||||
en: 'Data protection obligations are transferred to sub-processors',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Vertraglich vereinbart, dass Unterauftragnehmer gleichen Pflichten unterliegen',
|
||||
en: 'Contractually agreed that sub-processors are subject to same obligations',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 4 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-SUB-05',
|
||||
domain: 'SUBPROCESSOR',
|
||||
title: {
|
||||
de: 'Haftung für Unterauftragnehmer',
|
||||
en: 'Liability for sub-processors',
|
||||
},
|
||||
description: {
|
||||
de: 'Klare Haftungsregelung für Unterauftragnehmer',
|
||||
en: 'Clear liability provision for sub-processors',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Auftragsverarbeiter haftet für Unterauftragnehmer wie für eigenes Handeln',
|
||||
en: 'Processor is liable for sub-processors as for own actions',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 4 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// TOM - Technische/Organisatorische Maßnahmen
|
||||
// ==========================================
|
||||
|
||||
export const TOM_CONTROLS: Control[] = [
|
||||
{
|
||||
id: 'VND-TOM-01',
|
||||
domain: 'TOM',
|
||||
title: {
|
||||
de: 'TOM-Dokumentation vorhanden',
|
||||
en: 'TOM documentation available',
|
||||
},
|
||||
description: {
|
||||
de: 'Vollständige Dokumentation der technischen und organisatorischen Maßnahmen',
|
||||
en: 'Complete documentation of technical and organizational measures',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'TOM-Anlage vorhanden, aktuell, spezifisch für die Verarbeitung',
|
||||
en: 'TOM annex available, current, specific to the processing',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. c DSGVO', 'Art. 32 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TOM-02',
|
||||
domain: 'TOM',
|
||||
title: {
|
||||
de: 'Verschlüsselung',
|
||||
en: 'Encryption',
|
||||
},
|
||||
description: {
|
||||
de: 'Angemessene Verschlüsselung für Daten in Transit und at Rest',
|
||||
en: 'Appropriate encryption for data in transit and at rest',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'TLS 1.2+ für Transit, AES-256 für at Rest',
|
||||
en: 'TLS 1.2+ for transit, AES-256 for at rest',
|
||||
},
|
||||
requirements: ['Art. 32 Abs. 1 lit. a DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TOM-03',
|
||||
domain: 'TOM',
|
||||
title: {
|
||||
de: 'Zugriffskontrolle',
|
||||
en: 'Access control',
|
||||
},
|
||||
description: {
|
||||
de: 'Angemessene Zugriffskontrollmechanismen',
|
||||
en: 'Appropriate access control mechanisms',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Rollenbasierte Zugriffskontrolle, Least Privilege, Logging',
|
||||
en: 'Role-based access control, least privilege, logging',
|
||||
},
|
||||
requirements: ['Art. 32 Abs. 1 lit. b DSGVO', 'ISO 27001 A.9'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TOM-04',
|
||||
domain: 'TOM',
|
||||
title: {
|
||||
de: 'Verfügbarkeit und Wiederherstellung',
|
||||
en: 'Availability and recovery',
|
||||
},
|
||||
description: {
|
||||
de: 'Maßnahmen zur Sicherstellung der Verfügbarkeit und Wiederherstellung',
|
||||
en: 'Measures to ensure availability and recovery',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Backup-Konzept, DR-Plan, RTO/RPO definiert',
|
||||
en: 'Backup concept, DR plan, RTO/RPO defined',
|
||||
},
|
||||
requirements: ['Art. 32 Abs. 1 lit. b, c DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TOM-05',
|
||||
domain: 'TOM',
|
||||
title: {
|
||||
de: 'Regelmäßige TOM-Überprüfung',
|
||||
en: 'Regular TOM review',
|
||||
},
|
||||
description: {
|
||||
de: 'Regelmäßige Überprüfung und Aktualisierung der TOM',
|
||||
en: 'Regular review and update of TOM',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'TOM werden mindestens jährlich überprüft und bei Bedarf aktualisiert',
|
||||
en: 'TOM are reviewed at least annually and updated as needed',
|
||||
},
|
||||
requirements: ['Art. 32 Abs. 1 lit. d DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TOM-06',
|
||||
domain: 'TOM',
|
||||
title: {
|
||||
de: 'Penetrationstest',
|
||||
en: 'Penetration testing',
|
||||
},
|
||||
description: {
|
||||
de: 'Regelmäßige Penetrationstests der relevanten Systeme',
|
||||
en: 'Regular penetration testing of relevant systems',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Jährlicher Pentest, kritische Findings behoben',
|
||||
en: 'Annual pentest, critical findings resolved',
|
||||
},
|
||||
requirements: ['ISO 27001 A.12.6.1'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// CONTRACT - Vertragliche Grundlagen
|
||||
// ==========================================
|
||||
|
||||
export const CONTRACT_CONTROLS: Control[] = [
|
||||
{
|
||||
id: 'VND-CON-01',
|
||||
domain: 'CONTRACT',
|
||||
title: {
|
||||
de: 'Weisungsgebundenheit',
|
||||
en: 'Instruction binding',
|
||||
},
|
||||
description: {
|
||||
de: 'Auftragsverarbeiter ist an Weisungen gebunden',
|
||||
en: 'Processor is bound by instructions',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Weisungsgebundenheit explizit vereinbart, Hinweispflicht bei rechtswidrigen Weisungen',
|
||||
en: 'Instruction binding explicitly agreed, notification obligation for unlawful instructions',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. a DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-CON-02',
|
||||
domain: 'CONTRACT',
|
||||
title: {
|
||||
de: 'Vertraulichkeitsverpflichtung',
|
||||
en: 'Confidentiality obligation',
|
||||
},
|
||||
description: {
|
||||
de: 'Mitarbeiter sind zur Vertraulichkeit verpflichtet',
|
||||
en: 'Employees are obligated to confidentiality',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Vertraulichkeitsverpflichtung für alle Mitarbeiter mit Datenzugriff',
|
||||
en: 'Confidentiality obligation for all employees with data access',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. b DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-CON-03',
|
||||
domain: 'CONTRACT',
|
||||
title: {
|
||||
de: 'Gegenstand und Dauer der Verarbeitung',
|
||||
en: 'Subject and duration of processing',
|
||||
},
|
||||
description: {
|
||||
de: 'Klare Definition von Gegenstand und Dauer der Verarbeitung',
|
||||
en: 'Clear definition of subject and duration of processing',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Verarbeitungsgegenstand, Dauer, Art der Daten, Betroffene definiert',
|
||||
en: 'Processing subject, duration, type of data, data subjects defined',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-CON-04',
|
||||
domain: 'CONTRACT',
|
||||
title: {
|
||||
de: 'Schriftform/Textform',
|
||||
en: 'Written/text form',
|
||||
},
|
||||
description: {
|
||||
de: 'AVV in Schriftform oder elektronischem Format',
|
||||
en: 'DPA in written or electronic format',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'AVV in Schriftform oder elektronisch mit qualifizierter Signatur',
|
||||
en: 'DPA in written form or electronically with qualified signature',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 9 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// DATA_SUBJECT - Betroffenenrechte
|
||||
// ==========================================
|
||||
|
||||
export const DATA_SUBJECT_CONTROLS: Control[] = [
|
||||
{
|
||||
id: 'VND-DSR-01',
|
||||
domain: 'DATA_SUBJECT',
|
||||
title: {
|
||||
de: 'Unterstützung bei Betroffenenrechten',
|
||||
en: 'Support for data subject rights',
|
||||
},
|
||||
description: {
|
||||
de: 'Vendor unterstützt bei der Erfüllung von Betroffenenrechten',
|
||||
en: 'Vendor supports fulfillment of data subject rights',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Unterstützungspflicht vereinbart, Prozess zur Weiterleitung definiert',
|
||||
en: 'Support obligation agreed, process for forwarding defined',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. e DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-DSR-02',
|
||||
domain: 'DATA_SUBJECT',
|
||||
title: {
|
||||
de: 'Reaktionszeit für Anfragen',
|
||||
en: 'Response time for requests',
|
||||
},
|
||||
description: {
|
||||
de: 'Definierte Reaktionszeit für Betroffenenanfragen',
|
||||
en: 'Defined response time for data subject requests',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Reaktionszeit max. 5 Werktage, um Frist von 1 Monat einhalten zu können',
|
||||
en: 'Response time max. 5 business days to meet 1 month deadline',
|
||||
},
|
||||
requirements: ['Art. 12 Abs. 3 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// SECURITY - Sicherheit
|
||||
// ==========================================
|
||||
|
||||
export const SECURITY_CONTROLS: Control[] = [
|
||||
{
|
||||
id: 'VND-SEC-01',
|
||||
domain: 'SECURITY',
|
||||
title: {
|
||||
de: 'Sicherheitsbewertung',
|
||||
en: 'Security assessment',
|
||||
},
|
||||
description: {
|
||||
de: 'Regelmäßige Sicherheitsbewertung des Vendors',
|
||||
en: 'Regular security assessment of the vendor',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Sicherheitsfragebogen ausgefüllt, keine kritischen Lücken',
|
||||
en: 'Security questionnaire completed, no critical gaps',
|
||||
},
|
||||
requirements: ['Art. 32 DSGVO', 'ISO 27001 A.15.2.1'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-SEC-02',
|
||||
domain: 'SECURITY',
|
||||
title: {
|
||||
de: 'Vulnerability Management',
|
||||
en: 'Vulnerability management',
|
||||
},
|
||||
description: {
|
||||
de: 'Etabliertes Vulnerability Management beim Vendor',
|
||||
en: 'Established vulnerability management at the vendor',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Regelmäßige Schwachstellen-Scans, Patch-Management dokumentiert',
|
||||
en: 'Regular vulnerability scans, patch management documented',
|
||||
},
|
||||
requirements: ['ISO 27001 A.12.6'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-SEC-03',
|
||||
domain: 'SECURITY',
|
||||
title: {
|
||||
de: 'Mitarbeiter-Schulung',
|
||||
en: 'Employee training',
|
||||
},
|
||||
description: {
|
||||
de: 'Datenschutz-Schulung für Mitarbeiter des Vendors',
|
||||
en: 'Data protection training for vendor employees',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Regelmäßige Schulungen (mind. jährlich), Nachweis verfügbar',
|
||||
en: 'Regular training (at least annually), proof available',
|
||||
},
|
||||
requirements: ['Art. 39 Abs. 1 lit. b DSGVO'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// GOVERNANCE - Governance
|
||||
// ==========================================
|
||||
|
||||
export const GOVERNANCE_CONTROLS: Control[] = [
|
||||
{
|
||||
id: 'VND-GOV-01',
|
||||
domain: 'GOVERNANCE',
|
||||
title: {
|
||||
de: 'Datenschutzbeauftragter benannt',
|
||||
en: 'Data protection officer appointed',
|
||||
},
|
||||
description: {
|
||||
de: 'Vendor hat DSB benannt (wenn erforderlich)',
|
||||
en: 'Vendor has appointed DPO (if required)',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'DSB benannt und Kontaktdaten verfügbar',
|
||||
en: 'DPO appointed and contact details available',
|
||||
},
|
||||
requirements: ['Art. 37 DSGVO'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-GOV-02',
|
||||
domain: 'GOVERNANCE',
|
||||
title: {
|
||||
de: 'Verzeichnis der Verarbeitungstätigkeiten',
|
||||
en: 'Records of processing activities',
|
||||
},
|
||||
description: {
|
||||
de: 'Vendor führt eigenes Verarbeitungsverzeichnis',
|
||||
en: 'Vendor maintains own processing records',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Verzeichnis nach Art. 30 Abs. 2 DSGVO vorhanden',
|
||||
en: 'Records according to Art. 30(2) GDPR available',
|
||||
},
|
||||
requirements: ['Art. 30 Abs. 2 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-GOV-03',
|
||||
domain: 'GOVERNANCE',
|
||||
title: {
|
||||
de: 'Unterstützung bei DSFA',
|
||||
en: 'Support for DPIA',
|
||||
},
|
||||
description: {
|
||||
de: 'Vendor unterstützt bei Datenschutz-Folgenabschätzung',
|
||||
en: 'Vendor supports data protection impact assessment',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Unterstützungspflicht bei DSFA vertraglich vereinbart',
|
||||
en: 'Support obligation for DPIA contractually agreed',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. f DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
]
|
||||
115
admin-lehrer/lib/sdk/vendor-compliance/risk/controls-helpers.ts
Normal file
115
admin-lehrer/lib/sdk/vendor-compliance/risk/controls-helpers.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Controls Library - Helper Functions
|
||||
*
|
||||
* Query, filter, and analysis utilities for the controls library.
|
||||
*/
|
||||
|
||||
import { Control, ControlDomain, ReviewFrequency, LocalizedText } from '../types'
|
||||
import { CONTROLS_LIBRARY } from './controls-all'
|
||||
|
||||
/**
|
||||
* Get all controls
|
||||
*/
|
||||
export function getAllControls(): Control[] {
|
||||
return CONTROLS_LIBRARY
|
||||
}
|
||||
|
||||
/**
|
||||
* Get controls by domain
|
||||
*/
|
||||
export function getControlsByDomain(domain: ControlDomain): Control[] {
|
||||
return CONTROLS_LIBRARY.filter((c) => c.domain === domain)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get control by ID
|
||||
*/
|
||||
export function getControlById(id: string): Control | undefined {
|
||||
return CONTROLS_LIBRARY.find((c) => c.id === id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required controls
|
||||
*/
|
||||
export function getRequiredControls(): Control[] {
|
||||
return CONTROLS_LIBRARY.filter((c) => c.isRequired)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get controls by frequency
|
||||
*/
|
||||
export function getControlsByFrequency(frequency: ReviewFrequency): Control[] {
|
||||
return CONTROLS_LIBRARY.filter((c) => c.defaultFrequency === frequency)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get controls applicable to vendors
|
||||
*/
|
||||
export function getVendorControls(): Control[] {
|
||||
return CONTROLS_LIBRARY.filter((c) =>
|
||||
['TRANSFER', 'AUDIT', 'DELETION', 'INCIDENT', 'SUBPROCESSOR', 'TOM', 'CONTRACT', 'DATA_SUBJECT', 'SECURITY', 'GOVERNANCE'].includes(c.domain)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get controls applicable to processing activities
|
||||
*/
|
||||
export function getProcessingActivityControls(): Control[] {
|
||||
return CONTROLS_LIBRARY.filter((c) =>
|
||||
['TOM', 'DATA_SUBJECT', 'GOVERNANCE', 'SECURITY'].includes(c.domain)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Group controls by domain
|
||||
*/
|
||||
export function getControlsGroupedByDomain(): Map<ControlDomain, Control[]> {
|
||||
const grouped = new Map<ControlDomain, Control[]>()
|
||||
|
||||
for (const control of CONTROLS_LIBRARY) {
|
||||
const existing = grouped.get(control.domain) || []
|
||||
grouped.set(control.domain, [...existing, control])
|
||||
}
|
||||
|
||||
return grouped
|
||||
}
|
||||
|
||||
/**
|
||||
* Get domain metadata
|
||||
*/
|
||||
export function getControlDomainMeta(domain: ControlDomain): LocalizedText {
|
||||
const meta: Record<ControlDomain, LocalizedText> = {
|
||||
TRANSFER: { de: 'Drittlandtransfer', en: 'Third Country Transfer' },
|
||||
AUDIT: { de: 'Audit & Prüfung', en: 'Audit & Review' },
|
||||
DELETION: { de: 'Löschung', en: 'Deletion' },
|
||||
INCIDENT: { de: 'Incident Response', en: 'Incident Response' },
|
||||
SUBPROCESSOR: { de: 'Unterauftragnehmer', en: 'Sub-Processors' },
|
||||
TOM: { de: 'Technische/Org. Maßnahmen', en: 'Technical/Org. Measures' },
|
||||
CONTRACT: { de: 'Vertragliche Grundlagen', en: 'Contractual Basics' },
|
||||
DATA_SUBJECT: { de: 'Betroffenenrechte', en: 'Data Subject Rights' },
|
||||
SECURITY: { de: 'Sicherheit', en: 'Security' },
|
||||
GOVERNANCE: { de: 'Governance', en: 'Governance' },
|
||||
}
|
||||
|
||||
return meta[domain]
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate control coverage
|
||||
*/
|
||||
export function calculateControlCoverage(
|
||||
controlIds: string[],
|
||||
domain?: ControlDomain
|
||||
): { covered: number; total: number; percentage: number } {
|
||||
const targetControls = domain
|
||||
? getControlsByDomain(domain)
|
||||
: getRequiredControls()
|
||||
|
||||
const covered = targetControls.filter((c) => controlIds.includes(c.id)).length
|
||||
|
||||
return {
|
||||
covered,
|
||||
total: targetControls.length,
|
||||
percentage: targetControls.length > 0 ? Math.round((covered / targetControls.length) * 100) : 0,
|
||||
}
|
||||
}
|
||||
@@ -1,943 +1,40 @@
|
||||
/**
|
||||
* Controls Library
|
||||
*
|
||||
* Standard controls for vendor and processing activity compliance
|
||||
* Standard controls for vendor and processing activity compliance.
|
||||
* Barrel file that re-exports the merged array, domain arrays, and helpers.
|
||||
*/
|
||||
|
||||
import { Control, ControlDomain, ReviewFrequency, LocalizedText } from '../types'
|
||||
// Merged controls array
|
||||
export { CONTROLS_LIBRARY } from './controls-all'
|
||||
|
||||
// ==========================================
|
||||
// CONTROL DEFINITIONS
|
||||
// ==========================================
|
||||
// Domain-specific arrays
|
||||
export {
|
||||
TRANSFER_CONTROLS,
|
||||
AUDIT_CONTROLS,
|
||||
DELETION_CONTROLS,
|
||||
INCIDENT_CONTROLS,
|
||||
} from './controls-data-compliance'
|
||||
|
||||
export const CONTROLS_LIBRARY: Control[] = [
|
||||
// ==========================================
|
||||
// TRANSFER - Drittlandtransfer Controls
|
||||
// ==========================================
|
||||
{
|
||||
id: 'VND-TRF-01',
|
||||
domain: 'TRANSFER',
|
||||
title: {
|
||||
de: 'Drittlandtransfer nur mit Rechtsgrundlage',
|
||||
en: 'Third country transfer with legal basis',
|
||||
},
|
||||
description: {
|
||||
de: 'Drittlandtransfers erfolgen nur auf Basis von SCC, BCR oder Angemessenheitsbeschluss',
|
||||
en: 'Third country transfers only based on SCC, BCR or adequacy decision',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'SCC oder BCR vertraglich vereinbart ODER Angemessenheitsbeschluss vorhanden',
|
||||
en: 'SCC or BCR contractually agreed OR adequacy decision exists',
|
||||
},
|
||||
requirements: ['Art. 44-49 DSGVO', 'ISO 27001 A.15.1.2'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TRF-02',
|
||||
domain: 'TRANSFER',
|
||||
title: {
|
||||
de: 'Aktuelle Standardvertragsklauseln',
|
||||
en: 'Current Standard Contractual Clauses',
|
||||
},
|
||||
description: {
|
||||
de: 'Bei SCC-Nutzung: Verwendung der aktuellen EU-Kommission-Klauseln (2021)',
|
||||
en: 'When using SCC: Current EU Commission clauses (2021) are used',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'SCC 2021 (Durchführungsbeschluss (EU) 2021/914) verwendet',
|
||||
en: 'SCC 2021 (Implementing Decision (EU) 2021/914) used',
|
||||
},
|
||||
requirements: ['Art. 46 Abs. 2 lit. c DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TRF-03',
|
||||
domain: 'TRANSFER',
|
||||
title: {
|
||||
de: 'Transfer Impact Assessment (TIA)',
|
||||
en: 'Transfer Impact Assessment (TIA)',
|
||||
},
|
||||
description: {
|
||||
de: 'Bei Transfers in Drittländer ohne Angemessenheitsbeschluss ist TIA durchzuführen',
|
||||
en: 'TIA required for transfers to third countries without adequacy decision',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'TIA dokumentiert und bewertet Risiken als akzeptabel',
|
||||
en: 'TIA documented and risks assessed as acceptable',
|
||||
},
|
||||
requirements: ['Schrems II Urteil', 'EDSA Empfehlungen 01/2020'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TRF-04',
|
||||
domain: 'TRANSFER',
|
||||
title: {
|
||||
de: 'Zusätzliche Schutzmaßnahmen',
|
||||
en: 'Supplementary Measures',
|
||||
},
|
||||
description: {
|
||||
de: 'Bei Bedarf sind zusätzliche technische/organisatorische Maßnahmen implementiert',
|
||||
en: 'Supplementary technical/organizational measures implemented where needed',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Ergänzende Maßnahmen dokumentiert (Verschlüsselung, Pseudonymisierung, etc.)',
|
||||
en: 'Supplementary measures documented (encryption, pseudonymization, etc.)',
|
||||
},
|
||||
requirements: ['EDSA Empfehlungen 01/2020'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TRF-05',
|
||||
domain: 'TRANSFER',
|
||||
title: {
|
||||
de: 'Überwachung Angemessenheitsbeschlüsse',
|
||||
en: 'Monitoring Adequacy Decisions',
|
||||
},
|
||||
description: {
|
||||
de: 'Änderungen bei Angemessenheitsbeschlüssen werden überwacht',
|
||||
en: 'Changes to adequacy decisions are monitored',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Prozess zur Überwachung und Reaktion auf Änderungen etabliert',
|
||||
en: 'Process for monitoring and responding to changes established',
|
||||
},
|
||||
requirements: ['Art. 45 DSGVO'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'QUARTERLY',
|
||||
},
|
||||
export {
|
||||
SUBPROCESSOR_CONTROLS,
|
||||
TOM_CONTROLS,
|
||||
CONTRACT_CONTROLS,
|
||||
DATA_SUBJECT_CONTROLS,
|
||||
SECURITY_CONTROLS,
|
||||
GOVERNANCE_CONTROLS,
|
||||
} from './controls-data-operations'
|
||||
|
||||
// ==========================================
|
||||
// AUDIT - Auditrechte Controls
|
||||
// ==========================================
|
||||
{
|
||||
id: 'VND-AUD-01',
|
||||
domain: 'AUDIT',
|
||||
title: {
|
||||
de: 'Auditrecht vertraglich vereinbart',
|
||||
en: 'Audit right contractually agreed',
|
||||
},
|
||||
description: {
|
||||
de: 'Vertrag enthält wirksames Auditrecht ohne unangemessene Einschränkungen',
|
||||
en: 'Contract contains effective audit right without unreasonable restrictions',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Auditrecht im AVV enthalten, max. 30 Tage Vorlaufzeit, keine Ausschlussklausel',
|
||||
en: 'Audit right in DPA, max 30 days notice, no exclusion clause',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-AUD-02',
|
||||
domain: 'AUDIT',
|
||||
title: {
|
||||
de: 'Vor-Ort-Inspektionen möglich',
|
||||
en: 'On-site inspections possible',
|
||||
},
|
||||
description: {
|
||||
de: 'Vertrag erlaubt Vor-Ort-Inspektionen bei dem Auftragsverarbeiter',
|
||||
en: 'Contract allows on-site inspections at the processor',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Vor-Ort-Audit explizit erlaubt, Zugang zu relevanten Bereichen',
|
||||
en: 'On-site audit explicitly allowed, access to relevant areas',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-AUD-03',
|
||||
domain: 'AUDIT',
|
||||
title: {
|
||||
de: 'Aktuelle Zertifizierungen',
|
||||
en: 'Current Certifications',
|
||||
},
|
||||
description: {
|
||||
de: 'Relevante Sicherheitszertifizierungen sind aktuell und gültig',
|
||||
en: 'Relevant security certifications are current and valid',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'ISO 27001, SOC 2 oder vergleichbar, nicht abgelaufen',
|
||||
en: 'ISO 27001, SOC 2 or equivalent, not expired',
|
||||
},
|
||||
requirements: ['Art. 32 DSGVO', 'ISO 27001 A.15.1.1'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-AUD-04',
|
||||
domain: 'AUDIT',
|
||||
title: {
|
||||
de: 'Letzte Prüfung durchgeführt',
|
||||
en: 'Last review conducted',
|
||||
},
|
||||
description: {
|
||||
de: 'Vendor wurde innerhalb des Review-Zyklus geprüft',
|
||||
en: 'Vendor was reviewed within the review cycle',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Dokumentierte Prüfung innerhalb des festgelegten Intervalls',
|
||||
en: 'Documented review within the defined interval',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-AUD-05',
|
||||
domain: 'AUDIT',
|
||||
title: {
|
||||
de: 'Prüfberichte verfügbar',
|
||||
en: 'Audit reports available',
|
||||
},
|
||||
description: {
|
||||
de: 'Aktuelle Prüfberichte (SOC 2, Penetrationstest, etc.) liegen vor',
|
||||
en: 'Current audit reports (SOC 2, penetration test, etc.) are available',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Prüfberichte nicht älter als 12 Monate',
|
||||
en: 'Audit reports not older than 12 months',
|
||||
},
|
||||
requirements: ['ISO 27001 A.18.2.1'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// DELETION - Löschung Controls
|
||||
// ==========================================
|
||||
{
|
||||
id: 'VND-DEL-01',
|
||||
domain: 'DELETION',
|
||||
title: {
|
||||
de: 'Löschung/Rückgabe nach Vertragsende',
|
||||
en: 'Deletion/return after contract end',
|
||||
},
|
||||
description: {
|
||||
de: 'Klare Regelung zur Löschung oder Rückgabe aller Daten nach Vertragsende',
|
||||
en: 'Clear provision for deletion or return of all data after contract end',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Löschfrist max. 30 Tage, Löschbestätigung vorgesehen',
|
||||
en: 'Deletion within max 30 days, deletion confirmation provided',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-DEL-02',
|
||||
domain: 'DELETION',
|
||||
title: {
|
||||
de: 'Löschbestätigung',
|
||||
en: 'Deletion confirmation',
|
||||
},
|
||||
description: {
|
||||
de: 'Schriftliche Bestätigung der vollständigen Datenlöschung',
|
||||
en: 'Written confirmation of complete data deletion',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Löschbestätigung vertraglich vereinbart und einforderbar',
|
||||
en: 'Deletion confirmation contractually agreed and enforceable',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-DEL-03',
|
||||
domain: 'DELETION',
|
||||
title: {
|
||||
de: 'Löschung bei Unterauftragnehmern',
|
||||
en: 'Deletion at sub-processors',
|
||||
},
|
||||
description: {
|
||||
de: 'Löschpflicht erstreckt sich auf alle Unterauftragnehmer',
|
||||
en: 'Deletion obligation extends to all sub-processors',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Weitergabe der Löschpflicht an Unterauftragnehmer vertraglich vereinbart',
|
||||
en: 'Transfer of deletion obligation to sub-processors contractually agreed',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. g, d DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-DEL-04',
|
||||
domain: 'DELETION',
|
||||
title: {
|
||||
de: 'Backup-Löschung',
|
||||
en: 'Backup deletion',
|
||||
},
|
||||
description: {
|
||||
de: 'Daten werden auch aus Backups gelöscht',
|
||||
en: 'Data is also deleted from backups',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Backup-Löschung geregelt, max. Aufbewahrungsfrist für Backups definiert',
|
||||
en: 'Backup deletion regulated, max retention period for backups defined',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// INCIDENT - Incident Response Controls
|
||||
// ==========================================
|
||||
{
|
||||
id: 'VND-INC-01',
|
||||
domain: 'INCIDENT',
|
||||
title: {
|
||||
de: 'Meldepflicht bei Datenpannen',
|
||||
en: 'Data breach notification obligation',
|
||||
},
|
||||
description: {
|
||||
de: 'Unverzügliche Meldung von Datenschutzverletzungen',
|
||||
en: 'Immediate notification of data protection violations',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Meldepflicht vereinbart, Frist max. 24-48h, Mindestinhalte definiert',
|
||||
en: 'Notification obligation agreed, deadline max 24-48h, minimum content defined',
|
||||
},
|
||||
requirements: ['Art. 33 Abs. 2 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-INC-02',
|
||||
domain: 'INCIDENT',
|
||||
title: {
|
||||
de: 'Incident Response Plan',
|
||||
en: 'Incident Response Plan',
|
||||
},
|
||||
description: {
|
||||
de: 'Vendor hat dokumentierten Incident Response Plan',
|
||||
en: 'Vendor has documented incident response plan',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Incident Response Plan liegt vor und wurde getestet',
|
||||
en: 'Incident response plan exists and has been tested',
|
||||
},
|
||||
requirements: ['ISO 27001 A.16.1'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-INC-03',
|
||||
domain: 'INCIDENT',
|
||||
title: {
|
||||
de: 'Kontaktstelle für Incidents',
|
||||
en: 'Contact point for incidents',
|
||||
},
|
||||
description: {
|
||||
de: 'Definierte Kontaktstelle für Datenschutzvorfälle',
|
||||
en: 'Defined contact point for data protection incidents',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Kontaktdaten für Incident-Meldungen bekannt und aktuell',
|
||||
en: 'Contact details for incident reporting known and current',
|
||||
},
|
||||
requirements: ['Art. 33 Abs. 2 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'QUARTERLY',
|
||||
},
|
||||
{
|
||||
id: 'VND-INC-04',
|
||||
domain: 'INCIDENT',
|
||||
title: {
|
||||
de: 'Unterstützung bei Incident-Dokumentation',
|
||||
en: 'Support with incident documentation',
|
||||
},
|
||||
description: {
|
||||
de: 'Vendor unterstützt bei der Dokumentation von Vorfällen',
|
||||
en: 'Vendor supports documentation of incidents',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Unterstützungspflicht bei Dokumentation vertraglich vereinbart',
|
||||
en: 'Support obligation for documentation contractually agreed',
|
||||
},
|
||||
requirements: ['Art. 33 Abs. 5 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// SUBPROCESSOR - Unterauftragnehmer Controls
|
||||
// ==========================================
|
||||
{
|
||||
id: 'VND-SUB-01',
|
||||
domain: 'SUBPROCESSOR',
|
||||
title: {
|
||||
de: 'Genehmigungspflicht für Unterauftragnehmer',
|
||||
en: 'Approval requirement for sub-processors',
|
||||
},
|
||||
description: {
|
||||
de: 'Einsatz von Unterauftragnehmern nur mit Genehmigung',
|
||||
en: 'Use of sub-processors only with approval',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Genehmigungserfordernis (spezifisch oder allgemein mit Widerspruchsrecht) vereinbart',
|
||||
en: 'Approval requirement (specific or general with objection right) agreed',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 2, 4 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-SUB-02',
|
||||
domain: 'SUBPROCESSOR',
|
||||
title: {
|
||||
de: 'Aktuelle Unterauftragnehmer-Liste',
|
||||
en: 'Current sub-processor list',
|
||||
},
|
||||
description: {
|
||||
de: 'Vollständige und aktuelle Liste aller Unterauftragnehmer',
|
||||
en: 'Complete and current list of all sub-processors',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Liste liegt vor mit Name, Sitz, Verarbeitungszweck',
|
||||
en: 'List available with name, location, processing purpose',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 2 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'QUARTERLY',
|
||||
},
|
||||
{
|
||||
id: 'VND-SUB-03',
|
||||
domain: 'SUBPROCESSOR',
|
||||
title: {
|
||||
de: 'Informationspflicht bei Änderungen',
|
||||
en: 'Notification obligation for changes',
|
||||
},
|
||||
description: {
|
||||
de: 'Information über neue oder geänderte Unterauftragnehmer',
|
||||
en: 'Information about new or changed sub-processors',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Vorabinformation vereinbart, ausreichende Frist für Widerspruch',
|
||||
en: 'Advance notification agreed, sufficient time for objection',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 2 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-SUB-04',
|
||||
domain: 'SUBPROCESSOR',
|
||||
title: {
|
||||
de: 'Weitergabe der Datenschutzpflichten',
|
||||
en: 'Transfer of data protection obligations',
|
||||
},
|
||||
description: {
|
||||
de: 'Datenschutzpflichten werden an Unterauftragnehmer weitergegeben',
|
||||
en: 'Data protection obligations are transferred to sub-processors',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Vertraglich vereinbart, dass Unterauftragnehmer gleichen Pflichten unterliegen',
|
||||
en: 'Contractually agreed that sub-processors are subject to same obligations',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 4 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-SUB-05',
|
||||
domain: 'SUBPROCESSOR',
|
||||
title: {
|
||||
de: 'Haftung für Unterauftragnehmer',
|
||||
en: 'Liability for sub-processors',
|
||||
},
|
||||
description: {
|
||||
de: 'Klare Haftungsregelung für Unterauftragnehmer',
|
||||
en: 'Clear liability provision for sub-processors',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Auftragsverarbeiter haftet für Unterauftragnehmer wie für eigenes Handeln',
|
||||
en: 'Processor is liable for sub-processors as for own actions',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 4 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// TOM - Technische/Organisatorische Maßnahmen
|
||||
// ==========================================
|
||||
{
|
||||
id: 'VND-TOM-01',
|
||||
domain: 'TOM',
|
||||
title: {
|
||||
de: 'TOM-Dokumentation vorhanden',
|
||||
en: 'TOM documentation available',
|
||||
},
|
||||
description: {
|
||||
de: 'Vollständige Dokumentation der technischen und organisatorischen Maßnahmen',
|
||||
en: 'Complete documentation of technical and organizational measures',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'TOM-Anlage vorhanden, aktuell, spezifisch für die Verarbeitung',
|
||||
en: 'TOM annex available, current, specific to the processing',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. c DSGVO', 'Art. 32 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TOM-02',
|
||||
domain: 'TOM',
|
||||
title: {
|
||||
de: 'Verschlüsselung',
|
||||
en: 'Encryption',
|
||||
},
|
||||
description: {
|
||||
de: 'Angemessene Verschlüsselung für Daten in Transit und at Rest',
|
||||
en: 'Appropriate encryption for data in transit and at rest',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'TLS 1.2+ für Transit, AES-256 für at Rest',
|
||||
en: 'TLS 1.2+ for transit, AES-256 for at rest',
|
||||
},
|
||||
requirements: ['Art. 32 Abs. 1 lit. a DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TOM-03',
|
||||
domain: 'TOM',
|
||||
title: {
|
||||
de: 'Zugriffskontrolle',
|
||||
en: 'Access control',
|
||||
},
|
||||
description: {
|
||||
de: 'Angemessene Zugriffskontrollmechanismen',
|
||||
en: 'Appropriate access control mechanisms',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Rollenbasierte Zugriffskontrolle, Least Privilege, Logging',
|
||||
en: 'Role-based access control, least privilege, logging',
|
||||
},
|
||||
requirements: ['Art. 32 Abs. 1 lit. b DSGVO', 'ISO 27001 A.9'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TOM-04',
|
||||
domain: 'TOM',
|
||||
title: {
|
||||
de: 'Verfügbarkeit und Wiederherstellung',
|
||||
en: 'Availability and recovery',
|
||||
},
|
||||
description: {
|
||||
de: 'Maßnahmen zur Sicherstellung der Verfügbarkeit und Wiederherstellung',
|
||||
en: 'Measures to ensure availability and recovery',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Backup-Konzept, DR-Plan, RTO/RPO definiert',
|
||||
en: 'Backup concept, DR plan, RTO/RPO defined',
|
||||
},
|
||||
requirements: ['Art. 32 Abs. 1 lit. b, c DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TOM-05',
|
||||
domain: 'TOM',
|
||||
title: {
|
||||
de: 'Regelmäßige TOM-Überprüfung',
|
||||
en: 'Regular TOM review',
|
||||
},
|
||||
description: {
|
||||
de: 'Regelmäßige Überprüfung und Aktualisierung der TOM',
|
||||
en: 'Regular review and update of TOM',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'TOM werden mindestens jährlich überprüft und bei Bedarf aktualisiert',
|
||||
en: 'TOM are reviewed at least annually and updated as needed',
|
||||
},
|
||||
requirements: ['Art. 32 Abs. 1 lit. d DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-TOM-06',
|
||||
domain: 'TOM',
|
||||
title: {
|
||||
de: 'Penetrationstest',
|
||||
en: 'Penetration testing',
|
||||
},
|
||||
description: {
|
||||
de: 'Regelmäßige Penetrationstests der relevanten Systeme',
|
||||
en: 'Regular penetration testing of relevant systems',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Jährlicher Pentest, kritische Findings behoben',
|
||||
en: 'Annual pentest, critical findings resolved',
|
||||
},
|
||||
requirements: ['ISO 27001 A.12.6.1'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// CONTRACT - Vertragliche Grundlagen
|
||||
// ==========================================
|
||||
{
|
||||
id: 'VND-CON-01',
|
||||
domain: 'CONTRACT',
|
||||
title: {
|
||||
de: 'Weisungsgebundenheit',
|
||||
en: 'Instruction binding',
|
||||
},
|
||||
description: {
|
||||
de: 'Auftragsverarbeiter ist an Weisungen gebunden',
|
||||
en: 'Processor is bound by instructions',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Weisungsgebundenheit explizit vereinbart, Hinweispflicht bei rechtswidrigen Weisungen',
|
||||
en: 'Instruction binding explicitly agreed, notification obligation for unlawful instructions',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. a DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-CON-02',
|
||||
domain: 'CONTRACT',
|
||||
title: {
|
||||
de: 'Vertraulichkeitsverpflichtung',
|
||||
en: 'Confidentiality obligation',
|
||||
},
|
||||
description: {
|
||||
de: 'Mitarbeiter sind zur Vertraulichkeit verpflichtet',
|
||||
en: 'Employees are obligated to confidentiality',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Vertraulichkeitsverpflichtung für alle Mitarbeiter mit Datenzugriff',
|
||||
en: 'Confidentiality obligation for all employees with data access',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. b DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-CON-03',
|
||||
domain: 'CONTRACT',
|
||||
title: {
|
||||
de: 'Gegenstand und Dauer der Verarbeitung',
|
||||
en: 'Subject and duration of processing',
|
||||
},
|
||||
description: {
|
||||
de: 'Klare Definition von Gegenstand und Dauer der Verarbeitung',
|
||||
en: 'Clear definition of subject and duration of processing',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Verarbeitungsgegenstand, Dauer, Art der Daten, Betroffene definiert',
|
||||
en: 'Processing subject, duration, type of data, data subjects defined',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-CON-04',
|
||||
domain: 'CONTRACT',
|
||||
title: {
|
||||
de: 'Schriftform/Textform',
|
||||
en: 'Written/text form',
|
||||
},
|
||||
description: {
|
||||
de: 'AVV in Schriftform oder elektronischem Format',
|
||||
en: 'DPA in written or electronic format',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'AVV in Schriftform oder elektronisch mit qualifizierter Signatur',
|
||||
en: 'DPA in written form or electronically with qualified signature',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 9 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// DATA_SUBJECT - Betroffenenrechte
|
||||
// ==========================================
|
||||
{
|
||||
id: 'VND-DSR-01',
|
||||
domain: 'DATA_SUBJECT',
|
||||
title: {
|
||||
de: 'Unterstützung bei Betroffenenrechten',
|
||||
en: 'Support for data subject rights',
|
||||
},
|
||||
description: {
|
||||
de: 'Vendor unterstützt bei der Erfüllung von Betroffenenrechten',
|
||||
en: 'Vendor supports fulfillment of data subject rights',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Unterstützungspflicht vereinbart, Prozess zur Weiterleitung definiert',
|
||||
en: 'Support obligation agreed, process for forwarding defined',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. e DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-DSR-02',
|
||||
domain: 'DATA_SUBJECT',
|
||||
title: {
|
||||
de: 'Reaktionszeit für Anfragen',
|
||||
en: 'Response time for requests',
|
||||
},
|
||||
description: {
|
||||
de: 'Definierte Reaktionszeit für Betroffenenanfragen',
|
||||
en: 'Defined response time for data subject requests',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Reaktionszeit max. 5 Werktage, um Frist von 1 Monat einhalten zu können',
|
||||
en: 'Response time max. 5 business days to meet 1 month deadline',
|
||||
},
|
||||
requirements: ['Art. 12 Abs. 3 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// SECURITY - Sicherheit
|
||||
// ==========================================
|
||||
{
|
||||
id: 'VND-SEC-01',
|
||||
domain: 'SECURITY',
|
||||
title: {
|
||||
de: 'Sicherheitsbewertung',
|
||||
en: 'Security assessment',
|
||||
},
|
||||
description: {
|
||||
de: 'Regelmäßige Sicherheitsbewertung des Vendors',
|
||||
en: 'Regular security assessment of the vendor',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Sicherheitsfragebogen ausgefüllt, keine kritischen Lücken',
|
||||
en: 'Security questionnaire completed, no critical gaps',
|
||||
},
|
||||
requirements: ['Art. 32 DSGVO', 'ISO 27001 A.15.2.1'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-SEC-02',
|
||||
domain: 'SECURITY',
|
||||
title: {
|
||||
de: 'Vulnerability Management',
|
||||
en: 'Vulnerability management',
|
||||
},
|
||||
description: {
|
||||
de: 'Etabliertes Vulnerability Management beim Vendor',
|
||||
en: 'Established vulnerability management at the vendor',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Regelmäßige Schwachstellen-Scans, Patch-Management dokumentiert',
|
||||
en: 'Regular vulnerability scans, patch management documented',
|
||||
},
|
||||
requirements: ['ISO 27001 A.12.6'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-SEC-03',
|
||||
domain: 'SECURITY',
|
||||
title: {
|
||||
de: 'Mitarbeiter-Schulung',
|
||||
en: 'Employee training',
|
||||
},
|
||||
description: {
|
||||
de: 'Datenschutz-Schulung für Mitarbeiter des Vendors',
|
||||
en: 'Data protection training for vendor employees',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Regelmäßige Schulungen (mind. jährlich), Nachweis verfügbar',
|
||||
en: 'Regular training (at least annually), proof available',
|
||||
},
|
||||
requirements: ['Art. 39 Abs. 1 lit. b DSGVO'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// GOVERNANCE - Governance
|
||||
// ==========================================
|
||||
{
|
||||
id: 'VND-GOV-01',
|
||||
domain: 'GOVERNANCE',
|
||||
title: {
|
||||
de: 'Datenschutzbeauftragter benannt',
|
||||
en: 'Data protection officer appointed',
|
||||
},
|
||||
description: {
|
||||
de: 'Vendor hat DSB benannt (wenn erforderlich)',
|
||||
en: 'Vendor has appointed DPO (if required)',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'DSB benannt und Kontaktdaten verfügbar',
|
||||
en: 'DPO appointed and contact details available',
|
||||
},
|
||||
requirements: ['Art. 37 DSGVO'],
|
||||
isRequired: false,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-GOV-02',
|
||||
domain: 'GOVERNANCE',
|
||||
title: {
|
||||
de: 'Verzeichnis der Verarbeitungstätigkeiten',
|
||||
en: 'Records of processing activities',
|
||||
},
|
||||
description: {
|
||||
de: 'Vendor führt eigenes Verarbeitungsverzeichnis',
|
||||
en: 'Vendor maintains own processing records',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Verzeichnis nach Art. 30 Abs. 2 DSGVO vorhanden',
|
||||
en: 'Records according to Art. 30(2) GDPR available',
|
||||
},
|
||||
requirements: ['Art. 30 Abs. 2 DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
{
|
||||
id: 'VND-GOV-03',
|
||||
domain: 'GOVERNANCE',
|
||||
title: {
|
||||
de: 'Unterstützung bei DSFA',
|
||||
en: 'Support for DPIA',
|
||||
},
|
||||
description: {
|
||||
de: 'Vendor unterstützt bei Datenschutz-Folgenabschätzung',
|
||||
en: 'Vendor supports data protection impact assessment',
|
||||
},
|
||||
passCriteria: {
|
||||
de: 'Unterstützungspflicht bei DSFA vertraglich vereinbart',
|
||||
en: 'Support obligation for DPIA contractually agreed',
|
||||
},
|
||||
requirements: ['Art. 28 Abs. 3 lit. f DSGVO'],
|
||||
isRequired: true,
|
||||
defaultFrequency: 'ANNUAL',
|
||||
},
|
||||
]
|
||||
|
||||
// ==========================================
|
||||
// HELPER FUNCTIONS
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Get all controls
|
||||
*/
|
||||
export function getAllControls(): Control[] {
|
||||
return CONTROLS_LIBRARY
|
||||
}
|
||||
|
||||
/**
|
||||
* Get controls by domain
|
||||
*/
|
||||
export function getControlsByDomain(domain: ControlDomain): Control[] {
|
||||
return CONTROLS_LIBRARY.filter((c) => c.domain === domain)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get control by ID
|
||||
*/
|
||||
export function getControlById(id: string): Control | undefined {
|
||||
return CONTROLS_LIBRARY.find((c) => c.id === id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required controls
|
||||
*/
|
||||
export function getRequiredControls(): Control[] {
|
||||
return CONTROLS_LIBRARY.filter((c) => c.isRequired)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get controls by frequency
|
||||
*/
|
||||
export function getControlsByFrequency(frequency: ReviewFrequency): Control[] {
|
||||
return CONTROLS_LIBRARY.filter((c) => c.defaultFrequency === frequency)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get controls applicable to vendors
|
||||
*/
|
||||
export function getVendorControls(): Control[] {
|
||||
return CONTROLS_LIBRARY.filter((c) =>
|
||||
['TRANSFER', 'AUDIT', 'DELETION', 'INCIDENT', 'SUBPROCESSOR', 'TOM', 'CONTRACT', 'DATA_SUBJECT', 'SECURITY', 'GOVERNANCE'].includes(c.domain)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get controls applicable to processing activities
|
||||
*/
|
||||
export function getProcessingActivityControls(): Control[] {
|
||||
return CONTROLS_LIBRARY.filter((c) =>
|
||||
['TOM', 'DATA_SUBJECT', 'GOVERNANCE', 'SECURITY'].includes(c.domain)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Group controls by domain
|
||||
*/
|
||||
export function getControlsGroupedByDomain(): Map<ControlDomain, Control[]> {
|
||||
const grouped = new Map<ControlDomain, Control[]>()
|
||||
|
||||
for (const control of CONTROLS_LIBRARY) {
|
||||
const existing = grouped.get(control.domain) || []
|
||||
grouped.set(control.domain, [...existing, control])
|
||||
}
|
||||
|
||||
return grouped
|
||||
}
|
||||
|
||||
/**
|
||||
* Get domain metadata
|
||||
*/
|
||||
export function getControlDomainMeta(domain: ControlDomain): LocalizedText {
|
||||
const meta: Record<ControlDomain, LocalizedText> = {
|
||||
TRANSFER: { de: 'Drittlandtransfer', en: 'Third Country Transfer' },
|
||||
AUDIT: { de: 'Audit & Prüfung', en: 'Audit & Review' },
|
||||
DELETION: { de: 'Löschung', en: 'Deletion' },
|
||||
INCIDENT: { de: 'Incident Response', en: 'Incident Response' },
|
||||
SUBPROCESSOR: { de: 'Unterauftragnehmer', en: 'Sub-Processors' },
|
||||
TOM: { de: 'Technische/Org. Maßnahmen', en: 'Technical/Org. Measures' },
|
||||
CONTRACT: { de: 'Vertragliche Grundlagen', en: 'Contractual Basics' },
|
||||
DATA_SUBJECT: { de: 'Betroffenenrechte', en: 'Data Subject Rights' },
|
||||
SECURITY: { de: 'Sicherheit', en: 'Security' },
|
||||
GOVERNANCE: { de: 'Governance', en: 'Governance' },
|
||||
}
|
||||
|
||||
return meta[domain]
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate control coverage
|
||||
*/
|
||||
export function calculateControlCoverage(
|
||||
controlIds: string[],
|
||||
domain?: ControlDomain
|
||||
): { covered: number; total: number; percentage: number } {
|
||||
const targetControls = domain
|
||||
? getControlsByDomain(domain)
|
||||
: getRequiredControls()
|
||||
|
||||
const covered = targetControls.filter((c) => controlIds.includes(c.id)).length
|
||||
|
||||
return {
|
||||
covered,
|
||||
total: targetControls.length,
|
||||
percentage: targetControls.length > 0 ? Math.round((covered / targetControls.length) * 100) : 0,
|
||||
}
|
||||
}
|
||||
// Helper functions
|
||||
export {
|
||||
getAllControls,
|
||||
getControlsByDomain,
|
||||
getControlById,
|
||||
getRequiredControls,
|
||||
getControlsByFrequency,
|
||||
getVendorControls,
|
||||
getProcessingActivityControls,
|
||||
getControlsGroupedByDomain,
|
||||
getControlDomainMeta,
|
||||
calculateControlCoverage,
|
||||
} from './controls-helpers'
|
||||
|
||||
50
admin-lehrer/lib/sdk/vendor-compliance/types-audit.ts
Normal file
50
admin-lehrer/lib/sdk/vendor-compliance/types-audit.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Audit & Export Types
|
||||
*
|
||||
* Types for report generation and data export:
|
||||
* - Report types (VVT, RoPA, vendor audit, etc.)
|
||||
* - Export formats (PDF, DOCX, XLSX, JSON)
|
||||
* - Audit report snapshots
|
||||
*/
|
||||
|
||||
// ==========================================
|
||||
// ENUMS - EXPORT
|
||||
// ==========================================
|
||||
|
||||
export type ReportType =
|
||||
| 'VVT_EXPORT'
|
||||
| 'VENDOR_AUDIT'
|
||||
| 'ROPA'
|
||||
| 'MANAGEMENT_SUMMARY'
|
||||
| 'DPIA_INPUT'
|
||||
|
||||
export type ExportFormat = 'PDF' | 'DOCX' | 'XLSX' | 'JSON'
|
||||
|
||||
// ==========================================
|
||||
// INTERFACES - AUDIT REPORTS
|
||||
// ==========================================
|
||||
|
||||
export interface ReportScope {
|
||||
vendorIds?: string[]
|
||||
processingActivityIds?: string[]
|
||||
dateRange?: { from: Date; to: Date }
|
||||
}
|
||||
|
||||
export interface AuditReport {
|
||||
id: string
|
||||
tenantId: string
|
||||
|
||||
type: ReportType
|
||||
|
||||
// Scope
|
||||
scope: ReportScope
|
||||
|
||||
// Generierung
|
||||
format: ExportFormat
|
||||
storagePath: string
|
||||
generatedAt: Date
|
||||
generatedBy: string
|
||||
|
||||
// Snapshot-Daten (fuer Revisionssicherheit)
|
||||
snapshotHash: string // SHA-256 des Inhalts
|
||||
}
|
||||
58
admin-lehrer/lib/sdk/vendor-compliance/types-common.ts
Normal file
58
admin-lehrer/lib/sdk/vendor-compliance/types-common.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Common / Shared Types
|
||||
*
|
||||
* Foundational types used across all vendor-compliance domains:
|
||||
* LocalizedText, Address, Contact, ResponsibleParty, Organization
|
||||
*/
|
||||
|
||||
// ==========================================
|
||||
// LOCALIZED TEXT
|
||||
// ==========================================
|
||||
|
||||
export interface LocalizedText {
|
||||
de: string
|
||||
en: string
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// COMMON TYPES
|
||||
// ==========================================
|
||||
|
||||
export interface Address {
|
||||
street: string
|
||||
city: string
|
||||
postalCode: string
|
||||
country: string // ISO 3166-1 alpha-2
|
||||
state?: string
|
||||
}
|
||||
|
||||
export interface Contact {
|
||||
name: string
|
||||
email: string
|
||||
phone?: string
|
||||
department?: string
|
||||
role?: string
|
||||
}
|
||||
|
||||
export interface ResponsibleParty {
|
||||
organizationName: string
|
||||
legalForm?: string
|
||||
address: Address
|
||||
contact: Contact
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ORGANISATION / TENANT
|
||||
// ==========================================
|
||||
|
||||
export interface Organization {
|
||||
id: string
|
||||
name: string
|
||||
legalForm: string // GmbH, AG, e.V., etc.
|
||||
address: Address
|
||||
country: string // ISO 3166-1 alpha-2
|
||||
vatId?: string
|
||||
dpoContact: Contact // Datenschutzbeauftragter
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
117
admin-lehrer/lib/sdk/vendor-compliance/types-contract.ts
Normal file
117
admin-lehrer/lib/sdk/vendor-compliance/types-contract.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Contract Types
|
||||
*
|
||||
* Types for contract document management:
|
||||
* - Document types (AVV, MSA, SLA, SCC, etc.)
|
||||
* - Contract review and lifecycle statuses
|
||||
* - Contract document and version interfaces
|
||||
*/
|
||||
|
||||
import type { Address } from './types-common'
|
||||
|
||||
// ==========================================
|
||||
// ENUMS - CONTRACT
|
||||
// ==========================================
|
||||
|
||||
export type DocumentType =
|
||||
| 'AVV' // Auftragsverarbeitungsvertrag
|
||||
| 'MSA' // Master Service Agreement
|
||||
| 'SLA' // Service Level Agreement
|
||||
| 'SCC' // Standard Contractual Clauses
|
||||
| 'NDA' // Non-Disclosure Agreement
|
||||
| 'TOM_ANNEX' // TOM-Anlage
|
||||
| 'CERTIFICATION' // Zertifikat
|
||||
| 'SUB_PROCESSOR_LIST' // Unterauftragsverarbeiter-Liste
|
||||
| 'OTHER'
|
||||
|
||||
export type ContractReviewStatus =
|
||||
| 'PENDING'
|
||||
| 'IN_PROGRESS'
|
||||
| 'COMPLETED'
|
||||
| 'FAILED'
|
||||
|
||||
export type ContractStatus =
|
||||
| 'DRAFT'
|
||||
| 'SIGNED'
|
||||
| 'ACTIVE'
|
||||
| 'EXPIRED'
|
||||
| 'TERMINATED'
|
||||
|
||||
// ==========================================
|
||||
// INTERFACES - CONTRACT
|
||||
// ==========================================
|
||||
|
||||
export interface ContractParty {
|
||||
role: 'CONTROLLER' | 'PROCESSOR' | 'PARTY'
|
||||
name: string
|
||||
address?: Address
|
||||
signatoryName?: string
|
||||
signatoryRole?: string
|
||||
}
|
||||
|
||||
export interface ContractDocument {
|
||||
id: string
|
||||
tenantId: string
|
||||
vendorId: string
|
||||
|
||||
// Dokument
|
||||
fileName: string
|
||||
originalName: string
|
||||
mimeType: string
|
||||
fileSize: number
|
||||
storagePath: string // MinIO path
|
||||
|
||||
// Klassifikation
|
||||
documentType: DocumentType
|
||||
|
||||
// Versioning
|
||||
version: string
|
||||
previousVersionId?: string
|
||||
|
||||
// Metadaten (extrahiert)
|
||||
parties?: ContractParty[]
|
||||
effectiveDate?: Date
|
||||
expirationDate?: Date
|
||||
autoRenewal?: boolean
|
||||
renewalNoticePeriod?: number // Tage
|
||||
terminationNoticePeriod?: number // Tage
|
||||
|
||||
// Review Status
|
||||
reviewStatus: ContractReviewStatus
|
||||
reviewCompletedAt?: Date
|
||||
complianceScore?: number // 0-100
|
||||
|
||||
// Workflow
|
||||
status: ContractStatus
|
||||
signedAt?: Date
|
||||
|
||||
// Extracted text for search
|
||||
extractedText?: string
|
||||
pageCount?: number
|
||||
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
export interface DocumentVersion {
|
||||
id: string
|
||||
documentId: string
|
||||
version: string
|
||||
storagePath: string
|
||||
extractedText?: string
|
||||
pageCount: number
|
||||
createdAt: Date
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// FORM TYPES - CONTRACT
|
||||
// ==========================================
|
||||
|
||||
export interface ContractUploadData {
|
||||
vendorId: string
|
||||
documentType: DocumentType
|
||||
version: string
|
||||
effectiveDate?: Date
|
||||
expirationDate?: Date
|
||||
autoRenewal?: boolean
|
||||
}
|
||||
93
admin-lehrer/lib/sdk/vendor-compliance/types-finding.ts
Normal file
93
admin-lehrer/lib/sdk/vendor-compliance/types-finding.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Finding Types
|
||||
*
|
||||
* Types for compliance findings from contract reviews:
|
||||
* - Finding classification (type, category, severity)
|
||||
* - Citation references to source documents
|
||||
* - Finding workflow (status, resolution)
|
||||
*/
|
||||
|
||||
import type { LocalizedText } from './types-common'
|
||||
|
||||
// ==========================================
|
||||
// ENUMS - FINDINGS
|
||||
// ==========================================
|
||||
|
||||
export type FindingType =
|
||||
| 'OK' // Anforderung erfuellt
|
||||
| 'GAP' // Luecke/fehlend
|
||||
| 'RISK' // Risiko identifiziert
|
||||
| 'UNKNOWN' // Nicht eindeutig
|
||||
|
||||
export type FindingCategory =
|
||||
| 'AVV_CONTENT' // Art. 28 Abs. 3 Mindestinhalte
|
||||
| 'SUBPROCESSOR' // Unterauftragnehmer-Regelung
|
||||
| 'INCIDENT' // Incident-Meldepflichten
|
||||
| 'AUDIT_RIGHTS' // Audit-/Inspektionsrechte
|
||||
| 'DELETION' // Loeschung/Rueckgabe
|
||||
| 'TOM' // Technische/Org. Massnahmen
|
||||
| 'TRANSFER' // Drittlandtransfer
|
||||
| 'LIABILITY' // Haftung/Indemnity
|
||||
| 'SLA' // Verfuegbarkeit
|
||||
| 'DATA_SUBJECT_RIGHTS' // Betroffenenrechte
|
||||
| 'CONFIDENTIALITY' // Vertraulichkeit
|
||||
| 'INSTRUCTION' // Weisungsgebundenheit
|
||||
| 'TERMINATION' // Vertragsbeendigung
|
||||
| 'GENERAL' // Allgemein
|
||||
|
||||
export type FindingSeverity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
|
||||
|
||||
export type FindingStatus =
|
||||
| 'OPEN'
|
||||
| 'IN_PROGRESS'
|
||||
| 'RESOLVED'
|
||||
| 'ACCEPTED'
|
||||
| 'FALSE_POSITIVE'
|
||||
|
||||
// ==========================================
|
||||
// INTERFACES - FINDINGS
|
||||
// ==========================================
|
||||
|
||||
export interface Citation {
|
||||
documentId: string
|
||||
versionId?: string
|
||||
page: number
|
||||
startChar: number
|
||||
endChar: number
|
||||
quotedText: string
|
||||
quoteHash: string // SHA-256 zur Verifizierung
|
||||
}
|
||||
|
||||
export interface Finding {
|
||||
id: string
|
||||
tenantId: string
|
||||
contractId: string
|
||||
vendorId: string
|
||||
|
||||
// Klassifikation
|
||||
type: FindingType
|
||||
category: FindingCategory
|
||||
severity: FindingSeverity
|
||||
|
||||
// Inhalt
|
||||
title: LocalizedText
|
||||
description: LocalizedText
|
||||
recommendation?: LocalizedText
|
||||
|
||||
// Citations (Textstellen-Belege)
|
||||
citations: Citation[]
|
||||
|
||||
// Verknuepfung
|
||||
affectedRequirement?: string // z.B. "Art. 28 Abs. 3 lit. a DSGVO"
|
||||
triggeredControls: string[] // Control-IDs
|
||||
|
||||
// Workflow
|
||||
status: FindingStatus
|
||||
assignee?: string
|
||||
dueDate?: Date
|
||||
resolution?: string
|
||||
resolvedAt?: Date
|
||||
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
280
admin-lehrer/lib/sdk/vendor-compliance/types-helpers.ts
Normal file
280
admin-lehrer/lib/sdk/vendor-compliance/types-helpers.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* Helper Functions & Constants (Metadata)
|
||||
*
|
||||
* Utility functions for risk calculation, formatting, validation.
|
||||
* Localized metadata constants for enums (labels, articles, etc.).
|
||||
*/
|
||||
|
||||
import type { LocalizedText } from './types-common'
|
||||
import type {
|
||||
PersonalDataCategory,
|
||||
DataSubjectCategory,
|
||||
LegalBasisType,
|
||||
TransferMechanismType,
|
||||
} from './types-processing'
|
||||
import type {
|
||||
VendorRole,
|
||||
VendorStatus,
|
||||
ServiceCategory,
|
||||
} from './types-vendor'
|
||||
import type {
|
||||
DocumentType,
|
||||
ContractStatus,
|
||||
} from './types-contract'
|
||||
import type {
|
||||
FindingSeverity,
|
||||
} from './types-finding'
|
||||
import type {
|
||||
RiskLevel,
|
||||
} from './types-risk'
|
||||
import type {
|
||||
ProcessingActivityStatus,
|
||||
} from './types-processing'
|
||||
|
||||
// ==========================================
|
||||
// HELPER FUNCTIONS
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Calculate risk level from score
|
||||
*/
|
||||
export function getRiskLevelFromScore(score: number): RiskLevel {
|
||||
if (score <= 4) return 'LOW'
|
||||
if (score <= 9) return 'MEDIUM'
|
||||
if (score <= 16) return 'HIGH'
|
||||
return 'CRITICAL'
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate risk score from likelihood and impact
|
||||
*/
|
||||
export function calculateRiskScore(likelihood: number, impact: number): number {
|
||||
return likelihood * impact
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if data category is special (Art. 9 DSGVO)
|
||||
*/
|
||||
export function isSpecialCategory(category: PersonalDataCategory): boolean {
|
||||
const specialCategories: PersonalDataCategory[] = [
|
||||
'HEALTH_DATA',
|
||||
'GENETIC_DATA',
|
||||
'BIOMETRIC_DATA',
|
||||
'RACIAL_ETHNIC',
|
||||
'POLITICAL_OPINIONS',
|
||||
'RELIGIOUS_BELIEFS',
|
||||
'TRADE_UNION',
|
||||
'SEX_LIFE',
|
||||
'CRIMINAL_DATA',
|
||||
]
|
||||
return specialCategories.includes(category)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if country has adequacy decision
|
||||
*/
|
||||
export function hasAdequacyDecision(countryCode: string): boolean {
|
||||
const adequateCountries = [
|
||||
'AD', 'AR', 'CA', 'FO', 'GG', 'IL', 'IM', 'JP', 'JE', 'NZ', 'KR', 'CH', 'GB', 'UY',
|
||||
// EU/EEA countries
|
||||
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU',
|
||||
'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE',
|
||||
'IS', 'LI', 'NO',
|
||||
]
|
||||
return adequateCountries.includes(countryCode.toUpperCase())
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate VVT ID
|
||||
*/
|
||||
export function generateVVTId(existingIds: string[]): string {
|
||||
const year = new Date().getFullYear()
|
||||
const prefix = `VVT-${year}-`
|
||||
|
||||
const existingNumbers = existingIds
|
||||
.filter(id => id.startsWith(prefix))
|
||||
.map(id => parseInt(id.replace(prefix, ''), 10))
|
||||
.filter(n => !isNaN(n))
|
||||
|
||||
const nextNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1
|
||||
return `${prefix}${nextNumber.toString().padStart(3, '0')}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date for display
|
||||
*/
|
||||
export function formatDate(date: Date | string | undefined): string {
|
||||
if (!date) return '-'
|
||||
const d = typeof date === 'string' ? new Date(date) : date
|
||||
return d.toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get severity color class
|
||||
*/
|
||||
export function getSeverityColor(severity: FindingSeverity): string {
|
||||
switch (severity) {
|
||||
case 'LOW': return 'text-blue-600 bg-blue-100'
|
||||
case 'MEDIUM': return 'text-yellow-600 bg-yellow-100'
|
||||
case 'HIGH': return 'text-orange-600 bg-orange-100'
|
||||
case 'CRITICAL': return 'text-red-600 bg-red-100'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status color class
|
||||
*/
|
||||
export function getStatusColor(status: VendorStatus | ProcessingActivityStatus | ContractStatus): string {
|
||||
switch (status) {
|
||||
case 'ACTIVE':
|
||||
case 'APPROVED':
|
||||
case 'SIGNED':
|
||||
return 'text-green-600 bg-green-100'
|
||||
case 'DRAFT':
|
||||
case 'PENDING_REVIEW':
|
||||
return 'text-yellow-600 bg-yellow-100'
|
||||
case 'REVIEW':
|
||||
case 'INACTIVE':
|
||||
return 'text-blue-600 bg-blue-100'
|
||||
case 'ARCHIVED':
|
||||
case 'EXPIRED':
|
||||
case 'TERMINATED':
|
||||
return 'text-gray-600 bg-gray-100'
|
||||
default:
|
||||
return 'text-gray-600 bg-gray-100'
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// CONSTANTS - METADATA
|
||||
// ==========================================
|
||||
|
||||
export const DATA_SUBJECT_CATEGORY_META: Record<DataSubjectCategory, LocalizedText> = {
|
||||
EMPLOYEES: { de: 'Beschäftigte', en: 'Employees' },
|
||||
APPLICANTS: { de: 'Bewerber', en: 'Applicants' },
|
||||
CUSTOMERS: { de: 'Kunden', en: 'Customers' },
|
||||
PROSPECTIVE_CUSTOMERS: { de: 'Interessenten', en: 'Prospective Customers' },
|
||||
SUPPLIERS: { de: 'Lieferanten', en: 'Suppliers' },
|
||||
BUSINESS_PARTNERS: { de: 'Geschäftspartner', en: 'Business Partners' },
|
||||
VISITORS: { de: 'Besucher', en: 'Visitors' },
|
||||
WEBSITE_USERS: { de: 'Website-Nutzer', en: 'Website Users' },
|
||||
APP_USERS: { de: 'App-Nutzer', en: 'App Users' },
|
||||
NEWSLETTER_SUBSCRIBERS: { de: 'Newsletter-Abonnenten', en: 'Newsletter Subscribers' },
|
||||
MEMBERS: { de: 'Mitglieder', en: 'Members' },
|
||||
PATIENTS: { de: 'Patienten', en: 'Patients' },
|
||||
STUDENTS: { de: 'Schüler/Studenten', en: 'Students' },
|
||||
MINORS: { de: 'Minderjährige', en: 'Minors' },
|
||||
OTHER: { de: 'Sonstige', en: 'Other' },
|
||||
}
|
||||
|
||||
export const PERSONAL_DATA_CATEGORY_META: Record<PersonalDataCategory, { label: LocalizedText; isSpecial: boolean }> = {
|
||||
NAME: { label: { de: 'Name', en: 'Name' }, isSpecial: false },
|
||||
CONTACT: { label: { de: 'Kontaktdaten', en: 'Contact Data' }, isSpecial: false },
|
||||
ADDRESS: { label: { de: 'Adressdaten', en: 'Address Data' }, isSpecial: false },
|
||||
DOB: { label: { de: 'Geburtsdatum', en: 'Date of Birth' }, isSpecial: false },
|
||||
ID_NUMBER: { label: { de: 'Ausweisnummern', en: 'ID Numbers' }, isSpecial: false },
|
||||
SOCIAL_SECURITY: { label: { de: 'Sozialversicherungsnummer', en: 'Social Security Number' }, isSpecial: false },
|
||||
TAX_ID: { label: { de: 'Steuer-ID', en: 'Tax ID' }, isSpecial: false },
|
||||
BANK_ACCOUNT: { label: { de: 'Bankverbindung', en: 'Bank Account' }, isSpecial: false },
|
||||
PAYMENT_DATA: { label: { de: 'Zahlungsdaten', en: 'Payment Data' }, isSpecial: false },
|
||||
EMPLOYMENT_DATA: { label: { de: 'Beschäftigungsdaten', en: 'Employment Data' }, isSpecial: false },
|
||||
SALARY_DATA: { label: { de: 'Gehaltsdaten', en: 'Salary Data' }, isSpecial: false },
|
||||
EDUCATION_DATA: { label: { de: 'Bildungsdaten', en: 'Education Data' }, isSpecial: false },
|
||||
PHOTO_VIDEO: { label: { de: 'Fotos/Videos', en: 'Photos/Videos' }, isSpecial: false },
|
||||
IP_ADDRESS: { label: { de: 'IP-Adressen', en: 'IP Addresses' }, isSpecial: false },
|
||||
DEVICE_ID: { label: { de: 'Gerätekennungen', en: 'Device IDs' }, isSpecial: false },
|
||||
LOCATION_DATA: { label: { de: 'Standortdaten', en: 'Location Data' }, isSpecial: false },
|
||||
USAGE_DATA: { label: { de: 'Nutzungsdaten', en: 'Usage Data' }, isSpecial: false },
|
||||
COMMUNICATION_DATA: { label: { de: 'Kommunikationsdaten', en: 'Communication Data' }, isSpecial: false },
|
||||
CONTRACT_DATA: { label: { de: 'Vertragsdaten', en: 'Contract Data' }, isSpecial: false },
|
||||
LOGIN_DATA: { label: { de: 'Login-Daten', en: 'Login Data' }, isSpecial: false },
|
||||
HEALTH_DATA: { label: { de: 'Gesundheitsdaten', en: 'Health Data' }, isSpecial: true },
|
||||
GENETIC_DATA: { label: { de: 'Genetische Daten', en: 'Genetic Data' }, isSpecial: true },
|
||||
BIOMETRIC_DATA: { label: { de: 'Biometrische Daten', en: 'Biometric Data' }, isSpecial: true },
|
||||
RACIAL_ETHNIC: { label: { de: 'Rassische/Ethnische Herkunft', en: 'Racial/Ethnic Origin' }, isSpecial: true },
|
||||
POLITICAL_OPINIONS: { label: { de: 'Politische Meinungen', en: 'Political Opinions' }, isSpecial: true },
|
||||
RELIGIOUS_BELIEFS: { label: { de: 'Religiöse Überzeugungen', en: 'Religious Beliefs' }, isSpecial: true },
|
||||
TRADE_UNION: { label: { de: 'Gewerkschaftszugehörigkeit', en: 'Trade Union Membership' }, isSpecial: true },
|
||||
SEX_LIFE: { label: { de: 'Sexualleben/Orientierung', en: 'Sex Life/Orientation' }, isSpecial: true },
|
||||
CRIMINAL_DATA: { label: { de: 'Strafrechtliche Daten', en: 'Criminal Data' }, isSpecial: true },
|
||||
OTHER: { label: { de: 'Sonstige', en: 'Other' }, isSpecial: false },
|
||||
}
|
||||
|
||||
export const LEGAL_BASIS_META: Record<LegalBasisType, { label: LocalizedText; article: string }> = {
|
||||
CONSENT: { label: { de: 'Einwilligung', en: 'Consent' }, article: 'Art. 6 Abs. 1 lit. a DSGVO' },
|
||||
CONTRACT: { label: { de: 'Vertragserfüllung', en: 'Contract Performance' }, article: 'Art. 6 Abs. 1 lit. b DSGVO' },
|
||||
LEGAL_OBLIGATION: { label: { de: 'Rechtliche Verpflichtung', en: 'Legal Obligation' }, article: 'Art. 6 Abs. 1 lit. c DSGVO' },
|
||||
VITAL_INTEREST: { label: { de: 'Lebenswichtige Interessen', en: 'Vital Interests' }, article: 'Art. 6 Abs. 1 lit. d DSGVO' },
|
||||
PUBLIC_TASK: { label: { de: 'Öffentliche Aufgabe', en: 'Public Task' }, article: 'Art. 6 Abs. 1 lit. e DSGVO' },
|
||||
LEGITIMATE_INTEREST: { label: { de: 'Berechtigtes Interesse', en: 'Legitimate Interest' }, article: 'Art. 6 Abs. 1 lit. f DSGVO' },
|
||||
ART9_CONSENT: { label: { de: 'Ausdrückliche Einwilligung', en: 'Explicit Consent' }, article: 'Art. 9 Abs. 2 lit. a DSGVO' },
|
||||
ART9_EMPLOYMENT: { label: { de: 'Arbeitsrecht', en: 'Employment Law' }, article: 'Art. 9 Abs. 2 lit. b DSGVO' },
|
||||
ART9_VITAL_INTEREST: { label: { de: 'Lebenswichtige Interessen', en: 'Vital Interests' }, article: 'Art. 9 Abs. 2 lit. c DSGVO' },
|
||||
ART9_FOUNDATION: { label: { de: 'Stiftung/Verein', en: 'Foundation/Association' }, article: 'Art. 9 Abs. 2 lit. d DSGVO' },
|
||||
ART9_PUBLIC: { label: { de: 'Offenkundig öffentlich', en: 'Manifestly Public' }, article: 'Art. 9 Abs. 2 lit. e DSGVO' },
|
||||
ART9_LEGAL_CLAIMS: { label: { de: 'Rechtsansprüche', en: 'Legal Claims' }, article: 'Art. 9 Abs. 2 lit. f DSGVO' },
|
||||
ART9_PUBLIC_INTEREST: { label: { de: 'Öffentliches Interesse', en: 'Public Interest' }, article: 'Art. 9 Abs. 2 lit. g DSGVO' },
|
||||
ART9_HEALTH: { label: { de: 'Gesundheitsversorgung', en: 'Health Care' }, article: 'Art. 9 Abs. 2 lit. h DSGVO' },
|
||||
ART9_PUBLIC_HEALTH: { label: { de: 'Öffentliche Gesundheit', en: 'Public Health' }, article: 'Art. 9 Abs. 2 lit. i DSGVO' },
|
||||
ART9_ARCHIVING: { label: { de: 'Archivzwecke', en: 'Archiving Purposes' }, article: 'Art. 9 Abs. 2 lit. j DSGVO' },
|
||||
}
|
||||
|
||||
export const VENDOR_ROLE_META: Record<VendorRole, LocalizedText> = {
|
||||
PROCESSOR: { de: 'Auftragsverarbeiter', en: 'Processor' },
|
||||
JOINT_CONTROLLER: { de: 'Gemeinsam Verantwortlicher', en: 'Joint Controller' },
|
||||
CONTROLLER: { de: 'Eigenständiger Verantwortlicher', en: 'Independent Controller' },
|
||||
SUB_PROCESSOR: { de: 'Unterauftragnehmer', en: 'Sub-Processor' },
|
||||
THIRD_PARTY: { de: 'Dritter', en: 'Third Party' },
|
||||
}
|
||||
|
||||
export const SERVICE_CATEGORY_META: Record<ServiceCategory, LocalizedText> = {
|
||||
HOSTING: { de: 'Hosting', en: 'Hosting' },
|
||||
CLOUD_INFRASTRUCTURE: { de: 'Cloud-Infrastruktur', en: 'Cloud Infrastructure' },
|
||||
ANALYTICS: { de: 'Analytics', en: 'Analytics' },
|
||||
CRM: { de: 'CRM', en: 'CRM' },
|
||||
ERP: { de: 'ERP', en: 'ERP' },
|
||||
HR_SOFTWARE: { de: 'HR-Software', en: 'HR Software' },
|
||||
PAYMENT: { de: 'Zahlungsabwicklung', en: 'Payment Processing' },
|
||||
EMAIL: { de: 'E-Mail', en: 'Email' },
|
||||
MARKETING: { de: 'Marketing', en: 'Marketing' },
|
||||
SUPPORT: { de: 'Support', en: 'Support' },
|
||||
SECURITY: { de: 'Sicherheit', en: 'Security' },
|
||||
INTEGRATION: { de: 'Integration', en: 'Integration' },
|
||||
CONSULTING: { de: 'Beratung', en: 'Consulting' },
|
||||
LEGAL: { de: 'Rechtliches', en: 'Legal' },
|
||||
ACCOUNTING: { de: 'Buchhaltung', en: 'Accounting' },
|
||||
COMMUNICATION: { de: 'Kommunikation', en: 'Communication' },
|
||||
STORAGE: { de: 'Speicher', en: 'Storage' },
|
||||
BACKUP: { de: 'Backup', en: 'Backup' },
|
||||
CDN: { de: 'CDN', en: 'CDN' },
|
||||
OTHER: { de: 'Sonstige', en: 'Other' },
|
||||
}
|
||||
|
||||
export const DOCUMENT_TYPE_META: Record<DocumentType, LocalizedText> = {
|
||||
AVV: { de: 'Auftragsverarbeitungsvertrag', en: 'Data Processing Agreement' },
|
||||
MSA: { de: 'Rahmenvertrag', en: 'Master Service Agreement' },
|
||||
SLA: { de: 'Service Level Agreement', en: 'Service Level Agreement' },
|
||||
SCC: { de: 'Standardvertragsklauseln', en: 'Standard Contractual Clauses' },
|
||||
NDA: { de: 'Geheimhaltungsvereinbarung', en: 'Non-Disclosure Agreement' },
|
||||
TOM_ANNEX: { de: 'TOM-Anlage', en: 'TOM Annex' },
|
||||
CERTIFICATION: { de: 'Zertifikat', en: 'Certification' },
|
||||
SUB_PROCESSOR_LIST: { de: 'Unterauftragnehmer-Liste', en: 'Sub-Processor List' },
|
||||
OTHER: { de: 'Sonstige', en: 'Other' },
|
||||
}
|
||||
|
||||
export const TRANSFER_MECHANISM_META: Record<TransferMechanismType, LocalizedText> = {
|
||||
ADEQUACY_DECISION: { de: 'Angemessenheitsbeschluss', en: 'Adequacy Decision' },
|
||||
SCC_CONTROLLER: { de: 'SCC (Controller-to-Controller)', en: 'SCC (Controller-to-Controller)' },
|
||||
SCC_PROCESSOR: { de: 'SCC (Controller-to-Processor)', en: 'SCC (Controller-to-Processor)' },
|
||||
BCR: { de: 'Binding Corporate Rules', en: 'Binding Corporate Rules' },
|
||||
DEROGATION_CONSENT: { de: 'Ausdrückliche Einwilligung', en: 'Explicit Consent' },
|
||||
DEROGATION_CONTRACT: { de: 'Vertragserfüllung', en: 'Contract Performance' },
|
||||
DEROGATION_LEGAL: { de: 'Rechtsansprüche', en: 'Legal Claims' },
|
||||
DEROGATION_PUBLIC: { de: 'Öffentliches Interesse', en: 'Public Interest' },
|
||||
CERTIFICATION: { de: 'Zertifizierung', en: 'Certification' },
|
||||
CODE_OF_CONDUCT: { de: 'Verhaltensregeln', en: 'Code of Conduct' },
|
||||
}
|
||||
213
admin-lehrer/lib/sdk/vendor-compliance/types-processing.ts
Normal file
213
admin-lehrer/lib/sdk/vendor-compliance/types-processing.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* Processing Activity Types (VVT / RoPA)
|
||||
*
|
||||
* Types for Art. 30 DSGVO processing activities:
|
||||
* - Enums for data categories, legal bases, transfer mechanisms
|
||||
* - Interfaces for processing activities and related structures
|
||||
*/
|
||||
|
||||
import type {
|
||||
LocalizedText,
|
||||
Contact,
|
||||
ResponsibleParty,
|
||||
} from './types-common'
|
||||
|
||||
// ==========================================
|
||||
// ENUMS - VVT / PROCESSING ACTIVITIES
|
||||
// ==========================================
|
||||
|
||||
export type ProcessingActivityStatus = 'DRAFT' | 'REVIEW' | 'APPROVED' | 'ARCHIVED'
|
||||
|
||||
export type ProtectionLevel = 'LOW' | 'MEDIUM' | 'HIGH'
|
||||
|
||||
export type DataSubjectCategory =
|
||||
| 'EMPLOYEES' // Beschaeftigte
|
||||
| 'APPLICANTS' // Bewerber
|
||||
| 'CUSTOMERS' // Kunden
|
||||
| 'PROSPECTIVE_CUSTOMERS' // Interessenten
|
||||
| 'SUPPLIERS' // Lieferanten
|
||||
| 'BUSINESS_PARTNERS' // Geschaeftspartner
|
||||
| 'VISITORS' // Besucher
|
||||
| 'WEBSITE_USERS' // Website-Nutzer
|
||||
| 'APP_USERS' // App-Nutzer
|
||||
| 'NEWSLETTER_SUBSCRIBERS' // Newsletter-Abonnenten
|
||||
| 'MEMBERS' // Mitglieder
|
||||
| 'PATIENTS' // Patienten
|
||||
| 'STUDENTS' // Schueler/Studenten
|
||||
| 'MINORS' // Minderjaehrige
|
||||
| 'OTHER'
|
||||
|
||||
export type PersonalDataCategory =
|
||||
| 'NAME' // Name
|
||||
| 'CONTACT' // Kontaktdaten
|
||||
| 'ADDRESS' // Adressdaten
|
||||
| 'DOB' // Geburtsdatum
|
||||
| 'ID_NUMBER' // Ausweisnummern
|
||||
| 'SOCIAL_SECURITY' // Sozialversicherungsnummer
|
||||
| 'TAX_ID' // Steuer-ID
|
||||
| 'BANK_ACCOUNT' // Bankverbindung
|
||||
| 'PAYMENT_DATA' // Zahlungsdaten
|
||||
| 'EMPLOYMENT_DATA' // Beschaeftigungsdaten
|
||||
| 'SALARY_DATA' // Gehaltsdaten
|
||||
| 'EDUCATION_DATA' // Bildungsdaten
|
||||
| 'PHOTO_VIDEO' // Fotos/Videos
|
||||
| 'IP_ADDRESS' // IP-Adressen
|
||||
| 'DEVICE_ID' // Geraete-Kennungen
|
||||
| 'LOCATION_DATA' // Standortdaten
|
||||
| 'USAGE_DATA' // Nutzungsdaten
|
||||
| 'COMMUNICATION_DATA' // Kommunikationsdaten
|
||||
| 'CONTRACT_DATA' // Vertragsdaten
|
||||
| 'LOGIN_DATA' // Login-Daten
|
||||
// Besondere Kategorien Art. 9 DSGVO
|
||||
| 'HEALTH_DATA' // Gesundheitsdaten
|
||||
| 'GENETIC_DATA' // Genetische Daten
|
||||
| 'BIOMETRIC_DATA' // Biometrische Daten
|
||||
| 'RACIAL_ETHNIC' // Rassische/Ethnische Herkunft
|
||||
| 'POLITICAL_OPINIONS' // Politische Meinungen
|
||||
| 'RELIGIOUS_BELIEFS' // Religiose Ueberzeugungen
|
||||
| 'TRADE_UNION' // Gewerkschaftszugehoerigkeit
|
||||
| 'SEX_LIFE' // Sexualleben/Orientierung
|
||||
// Art. 10 DSGVO
|
||||
| 'CRIMINAL_DATA' // Strafrechtliche Daten
|
||||
| 'OTHER'
|
||||
|
||||
export type RecipientCategoryType =
|
||||
| 'INTERNAL' // Interne Stellen
|
||||
| 'GROUP_COMPANY' // Konzernunternehmen
|
||||
| 'PROCESSOR' // Auftragsverarbeiter
|
||||
| 'CONTROLLER' // Verantwortlicher
|
||||
| 'AUTHORITY' // Behoerden
|
||||
| 'OTHER'
|
||||
|
||||
export type LegalBasisType =
|
||||
// Art. 6 Abs. 1 DSGVO
|
||||
| 'CONSENT' // lit. a - Einwilligung
|
||||
| 'CONTRACT' // lit. b - Vertragsdurchfuehrung
|
||||
| 'LEGAL_OBLIGATION' // lit. c - Rechtliche Verpflichtung
|
||||
| 'VITAL_INTEREST' // lit. d - Lebenswichtige Interessen
|
||||
| 'PUBLIC_TASK' // lit. e - Oeffentliche Aufgabe
|
||||
| 'LEGITIMATE_INTEREST' // lit. f - Berechtigtes Interesse
|
||||
// Art. 9 Abs. 2 DSGVO (besondere Kategorien)
|
||||
| 'ART9_CONSENT' // lit. a - Ausdrueckliche Einwilligung
|
||||
| 'ART9_EMPLOYMENT' // lit. b - Arbeitsrecht
|
||||
| 'ART9_VITAL_INTEREST' // lit. c - Lebenswichtige Interessen
|
||||
| 'ART9_FOUNDATION' // lit. d - Stiftung/Verein
|
||||
| 'ART9_PUBLIC' // lit. e - Offenkundig oeffentlich
|
||||
| 'ART9_LEGAL_CLAIMS' // lit. f - Rechtsansprueche
|
||||
| 'ART9_PUBLIC_INTEREST'// lit. g - Oeffentliches Interesse
|
||||
| 'ART9_HEALTH' // lit. h - Gesundheitsversorgung
|
||||
| 'ART9_PUBLIC_HEALTH' // lit. i - Oeffentliche Gesundheit
|
||||
| 'ART9_ARCHIVING' // lit. j - Archivzwecke
|
||||
|
||||
export type TransferMechanismType =
|
||||
| 'ADEQUACY_DECISION' // Angemessenheitsbeschluss
|
||||
| 'SCC_CONTROLLER' // SCC Controller-to-Controller
|
||||
| 'SCC_PROCESSOR' // SCC Controller-to-Processor
|
||||
| 'BCR' // Binding Corporate Rules
|
||||
| 'DEROGATION_CONSENT' // Ausdrueckliche Einwilligung
|
||||
| 'DEROGATION_CONTRACT' // Vertragserfuellung
|
||||
| 'DEROGATION_LEGAL' // Rechtsansprueche
|
||||
| 'DEROGATION_PUBLIC' // Oeffentliches Interesse
|
||||
| 'CERTIFICATION' // Zertifizierung
|
||||
| 'CODE_OF_CONDUCT' // Verhaltensregeln
|
||||
|
||||
export type DataSourceType =
|
||||
| 'DATA_SUBJECT' // Betroffene Person selbst
|
||||
| 'THIRD_PARTY' // Dritte
|
||||
| 'PUBLIC_SOURCE' // Oeffentliche Quellen
|
||||
| 'AUTOMATED' // Automatisiert generiert
|
||||
| 'EMPLOYEE' // Mitarbeiter
|
||||
| 'OTHER'
|
||||
|
||||
// ==========================================
|
||||
// INTERFACES - VVT / PROCESSING ACTIVITIES
|
||||
// ==========================================
|
||||
|
||||
export interface LegalBasis {
|
||||
type: LegalBasisType
|
||||
description?: string
|
||||
reference?: string // z.B. "§ 26 BDSG"
|
||||
}
|
||||
|
||||
export interface RecipientCategory {
|
||||
type: RecipientCategoryType
|
||||
name: string
|
||||
description?: string
|
||||
isThirdCountry?: boolean
|
||||
country?: string
|
||||
}
|
||||
|
||||
export interface ThirdCountryTransfer {
|
||||
country: string // ISO 3166-1 alpha-2
|
||||
recipient: string
|
||||
transferMechanism: TransferMechanismType
|
||||
sccVersion?: string
|
||||
tiaCompleted?: boolean
|
||||
tiaDate?: Date
|
||||
additionalMeasures?: string[]
|
||||
}
|
||||
|
||||
export interface RetentionPeriod {
|
||||
duration?: number // in Monaten
|
||||
durationUnit?: 'DAYS' | 'MONTHS' | 'YEARS'
|
||||
description: LocalizedText
|
||||
legalBasis?: string // z.B. "HGB § 257", "AO § 147"
|
||||
deletionProcedure?: string
|
||||
}
|
||||
|
||||
export interface DataSource {
|
||||
type: DataSourceType
|
||||
description?: string
|
||||
}
|
||||
|
||||
export interface SystemReference {
|
||||
systemId: string
|
||||
name: string
|
||||
description?: string
|
||||
type?: string // CRM, ERP, etc.
|
||||
}
|
||||
|
||||
export interface DataFlow {
|
||||
sourceSystem?: string
|
||||
targetSystem?: string
|
||||
description: string
|
||||
dataCategories: PersonalDataCategory[]
|
||||
}
|
||||
|
||||
export interface ProcessingActivity {
|
||||
id: string
|
||||
tenantId: string
|
||||
|
||||
// Pflichtfelder Art. 30(1) DSGVO
|
||||
vvtId: string // Eindeutige VVT-Nummer (z.B. VVT-2024-001)
|
||||
name: LocalizedText
|
||||
responsible: ResponsibleParty
|
||||
dpoContact?: Contact
|
||||
purposes: LocalizedText[]
|
||||
dataSubjectCategories: DataSubjectCategory[]
|
||||
personalDataCategories: PersonalDataCategory[]
|
||||
recipientCategories: RecipientCategory[]
|
||||
thirdCountryTransfers: ThirdCountryTransfer[]
|
||||
retentionPeriod: RetentionPeriod
|
||||
technicalMeasures: string[] // TOM-Referenzen
|
||||
|
||||
// Empfohlene Zusatzfelder
|
||||
legalBasis: LegalBasis[]
|
||||
dataSources: DataSource[]
|
||||
systems: SystemReference[]
|
||||
dataFlows: DataFlow[]
|
||||
protectionLevel: ProtectionLevel
|
||||
dpiaRequired: boolean
|
||||
dpiaJustification?: string
|
||||
subProcessors: string[] // Vendor-IDs
|
||||
legalRetentionBasis?: string
|
||||
|
||||
// Workflow
|
||||
status: ProcessingActivityStatus
|
||||
owner: string
|
||||
lastReviewDate?: Date
|
||||
nextReviewDate?: Date
|
||||
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
147
admin-lehrer/lib/sdk/vendor-compliance/types-risk.ts
Normal file
147
admin-lehrer/lib/sdk/vendor-compliance/types-risk.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Risk & Controls Types
|
||||
*
|
||||
* Types for risk assessment and control management:
|
||||
* - Risk scoring (likelihood x impact matrix)
|
||||
* - Control definitions and instances
|
||||
* - Evidence tracking
|
||||
*/
|
||||
|
||||
import type { LocalizedText } from './types-common'
|
||||
import type { ReviewFrequency } from './types-vendor'
|
||||
|
||||
// ==========================================
|
||||
// ENUMS - RISK & CONTROLS
|
||||
// ==========================================
|
||||
|
||||
export type ControlDomain =
|
||||
| 'TRANSFER' // Drittlandtransfer
|
||||
| 'AUDIT' // Auditrechte
|
||||
| 'DELETION' // Loeschung
|
||||
| 'INCIDENT' // Incident Response
|
||||
| 'SUBPROCESSOR' // Unterauftragnehmer
|
||||
| 'TOM' // Tech/Org Massnahmen
|
||||
| 'CONTRACT' // Vertragliche Grundlagen
|
||||
| 'DATA_SUBJECT' // Betroffenenrechte
|
||||
| 'SECURITY' // Sicherheit
|
||||
| 'GOVERNANCE' // Governance
|
||||
|
||||
export type ControlStatus =
|
||||
| 'PASS'
|
||||
| 'PARTIAL'
|
||||
| 'FAIL'
|
||||
| 'NOT_APPLICABLE'
|
||||
| 'PLANNED'
|
||||
|
||||
export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
|
||||
|
||||
export type EntityType = 'VENDOR' | 'PROCESSING_ACTIVITY' | 'CONTRACT'
|
||||
|
||||
export type EvidenceType = 'DOCUMENT' | 'SCREENSHOT' | 'LINK' | 'ATTESTATION'
|
||||
|
||||
// ==========================================
|
||||
// INTERFACES - RISK & CONTROLS
|
||||
// ==========================================
|
||||
|
||||
export interface RiskFactor {
|
||||
id: string
|
||||
name: LocalizedText
|
||||
category: string
|
||||
weight: number
|
||||
value: number // 1-5
|
||||
rationale?: string
|
||||
}
|
||||
|
||||
export interface RiskScore {
|
||||
likelihood: 1 | 2 | 3 | 4 | 5
|
||||
impact: 1 | 2 | 3 | 4 | 5
|
||||
score: number // likelihood * impact (1-25)
|
||||
level: RiskLevel
|
||||
rationale: string
|
||||
}
|
||||
|
||||
export interface RiskAssessment {
|
||||
id: string
|
||||
tenantId: string
|
||||
entityType: EntityType
|
||||
entityId: string
|
||||
|
||||
// Bewertung
|
||||
inherentRisk: RiskScore
|
||||
residualRisk: RiskScore
|
||||
|
||||
// Faktoren
|
||||
riskFactors: RiskFactor[]
|
||||
mitigatingControls: string[] // Control-IDs
|
||||
|
||||
// Workflow
|
||||
assessedBy: string
|
||||
assessedAt: Date
|
||||
approvedBy?: string
|
||||
approvedAt?: Date
|
||||
|
||||
nextAssessmentDate: Date
|
||||
}
|
||||
|
||||
export interface Control {
|
||||
id: string // z.B. VND-TRF-01
|
||||
domain: ControlDomain
|
||||
|
||||
title: LocalizedText
|
||||
description: LocalizedText
|
||||
passCriteria: LocalizedText
|
||||
|
||||
// Mapping
|
||||
requirements: string[] // Art. 28 Abs. 3 lit. a, ISO 27001 A.15.1.2
|
||||
|
||||
// Standard
|
||||
isRequired: boolean
|
||||
defaultFrequency: ReviewFrequency
|
||||
}
|
||||
|
||||
export interface ControlInstance {
|
||||
id: string
|
||||
tenantId: string
|
||||
controlId: string
|
||||
entityType: EntityType
|
||||
entityId: string
|
||||
|
||||
// Status
|
||||
status: ControlStatus
|
||||
|
||||
// Evidenz
|
||||
evidenceIds: string[]
|
||||
|
||||
// Workflow
|
||||
lastAssessedAt: Date
|
||||
lastAssessedBy: string
|
||||
nextAssessmentDate: Date
|
||||
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export interface Evidence {
|
||||
id: string
|
||||
tenantId: string
|
||||
controlInstanceId: string
|
||||
|
||||
type: EvidenceType
|
||||
title: string
|
||||
description?: string
|
||||
|
||||
// Fuer Dokumente
|
||||
storagePath?: string
|
||||
fileName?: string
|
||||
|
||||
// Fuer Links
|
||||
url?: string
|
||||
|
||||
// Fuer Attestation
|
||||
attestedBy?: string
|
||||
attestedAt?: Date
|
||||
|
||||
validFrom: Date
|
||||
validUntil?: Date
|
||||
|
||||
createdAt: Date
|
||||
}
|
||||
263
admin-lehrer/lib/sdk/vendor-compliance/types-state.ts
Normal file
263
admin-lehrer/lib/sdk/vendor-compliance/types-state.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* State Management Types
|
||||
*
|
||||
* Types for React context state management:
|
||||
* - Reducer actions (VendorComplianceAction)
|
||||
* - State shape (VendorComplianceState)
|
||||
* - Context value with computed properties and action methods
|
||||
* - Statistics and API response types
|
||||
* - Form data types
|
||||
*/
|
||||
|
||||
import type React from 'react'
|
||||
import type { LocalizedText, Contact, ResponsibleParty, Address } from './types-common'
|
||||
import type {
|
||||
ProcessingActivity,
|
||||
ProcessingActivityStatus,
|
||||
DataSubjectCategory,
|
||||
PersonalDataCategory,
|
||||
RecipientCategory,
|
||||
ThirdCountryTransfer,
|
||||
RetentionPeriod,
|
||||
LegalBasis,
|
||||
DataSource,
|
||||
SystemReference,
|
||||
DataFlow,
|
||||
ProtectionLevel,
|
||||
TransferMechanismType,
|
||||
} from './types-processing'
|
||||
import type {
|
||||
Vendor,
|
||||
VendorStatus,
|
||||
VendorRole,
|
||||
ServiceCategory,
|
||||
DataAccessLevel,
|
||||
ProcessingLocation,
|
||||
Certification,
|
||||
ReviewFrequency,
|
||||
} from './types-vendor'
|
||||
import type {
|
||||
ContractDocument,
|
||||
ContractStatus,
|
||||
DocumentType,
|
||||
} from './types-contract'
|
||||
import type {
|
||||
Finding,
|
||||
FindingType,
|
||||
FindingSeverity,
|
||||
} from './types-finding'
|
||||
import type {
|
||||
Control,
|
||||
ControlInstance,
|
||||
RiskAssessment,
|
||||
RiskLevel,
|
||||
} from './types-risk'
|
||||
import type { ExportFormat } from './types-audit'
|
||||
|
||||
// ==========================================
|
||||
// STATE MANAGEMENT - ACTIONS
|
||||
// ==========================================
|
||||
|
||||
export type VendorComplianceAction =
|
||||
// Processing Activities
|
||||
| { type: 'SET_PROCESSING_ACTIVITIES'; payload: ProcessingActivity[] }
|
||||
| { type: 'ADD_PROCESSING_ACTIVITY'; payload: ProcessingActivity }
|
||||
| { type: 'UPDATE_PROCESSING_ACTIVITY'; payload: { id: string; data: Partial<ProcessingActivity> } }
|
||||
| { type: 'DELETE_PROCESSING_ACTIVITY'; payload: string }
|
||||
// Vendors
|
||||
| { type: 'SET_VENDORS'; payload: Vendor[] }
|
||||
| { type: 'ADD_VENDOR'; payload: Vendor }
|
||||
| { type: 'UPDATE_VENDOR'; payload: { id: string; data: Partial<Vendor> } }
|
||||
| { type: 'DELETE_VENDOR'; payload: string }
|
||||
// Contracts
|
||||
| { type: 'SET_CONTRACTS'; payload: ContractDocument[] }
|
||||
| { type: 'ADD_CONTRACT'; payload: ContractDocument }
|
||||
| { type: 'UPDATE_CONTRACT'; payload: { id: string; data: Partial<ContractDocument> } }
|
||||
| { type: 'DELETE_CONTRACT'; payload: string }
|
||||
// Findings
|
||||
| { type: 'SET_FINDINGS'; payload: Finding[] }
|
||||
| { type: 'ADD_FINDINGS'; payload: Finding[] }
|
||||
| { type: 'UPDATE_FINDING'; payload: { id: string; data: Partial<Finding> } }
|
||||
// Controls
|
||||
| { type: 'SET_CONTROLS'; payload: Control[] }
|
||||
| { type: 'SET_CONTROL_INSTANCES'; payload: ControlInstance[] }
|
||||
| { type: 'UPDATE_CONTROL_INSTANCE'; payload: { id: string; data: Partial<ControlInstance> } }
|
||||
// Risk Assessments
|
||||
| { type: 'SET_RISK_ASSESSMENTS'; payload: RiskAssessment[] }
|
||||
| { type: 'UPDATE_RISK_ASSESSMENT'; payload: { id: string; data: Partial<RiskAssessment> } }
|
||||
// UI State
|
||||
| { type: 'SET_LOADING'; payload: boolean }
|
||||
| { type: 'SET_ERROR'; payload: string | null }
|
||||
| { type: 'SET_SELECTED_VENDOR'; payload: string | null }
|
||||
| { type: 'SET_SELECTED_ACTIVITY'; payload: string | null }
|
||||
| { type: 'SET_ACTIVE_TAB'; payload: string }
|
||||
|
||||
// ==========================================
|
||||
// STATE MANAGEMENT - STATE
|
||||
// ==========================================
|
||||
|
||||
export interface VendorComplianceState {
|
||||
// Data
|
||||
processingActivities: ProcessingActivity[]
|
||||
vendors: Vendor[]
|
||||
contracts: ContractDocument[]
|
||||
findings: Finding[]
|
||||
controls: Control[]
|
||||
controlInstances: ControlInstance[]
|
||||
riskAssessments: RiskAssessment[]
|
||||
|
||||
// UI State
|
||||
isLoading: boolean
|
||||
error: string | null
|
||||
selectedVendorId: string | null
|
||||
selectedActivityId: string | null
|
||||
activeTab: string
|
||||
|
||||
// Metadata
|
||||
lastModified: Date | null
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// STATISTICS INTERFACES
|
||||
// ==========================================
|
||||
|
||||
export interface VendorStatistics {
|
||||
total: number
|
||||
byStatus: Record<VendorStatus, number>
|
||||
byRole: Record<VendorRole, number>
|
||||
byRiskLevel: Record<RiskLevel, number>
|
||||
pendingReviews: number
|
||||
withExpiredContracts: number
|
||||
}
|
||||
|
||||
export interface ComplianceStatistics {
|
||||
averageComplianceScore: number
|
||||
findingsByType: Record<FindingType, number>
|
||||
findingsBySeverity: Record<FindingSeverity, number>
|
||||
openFindings: number
|
||||
resolvedFindings: number
|
||||
controlPassRate: number
|
||||
}
|
||||
|
||||
export interface RiskOverview {
|
||||
averageInherentRisk: number
|
||||
averageResidualRisk: number
|
||||
highRiskVendors: number
|
||||
criticalFindings: number
|
||||
transfersToThirdCountries: number
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// CONTEXT VALUE
|
||||
// ==========================================
|
||||
|
||||
export interface VendorComplianceContextValue extends VendorComplianceState {
|
||||
// Dispatch
|
||||
dispatch: React.Dispatch<VendorComplianceAction>
|
||||
|
||||
// Computed
|
||||
vendorStats: VendorStatistics
|
||||
complianceStats: ComplianceStatistics
|
||||
riskOverview: RiskOverview
|
||||
|
||||
// Actions - Processing Activities
|
||||
createProcessingActivity: (data: Omit<ProcessingActivity, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>) => Promise<ProcessingActivity>
|
||||
updateProcessingActivity: (id: string, data: Partial<ProcessingActivity>) => Promise<void>
|
||||
deleteProcessingActivity: (id: string) => Promise<void>
|
||||
duplicateProcessingActivity: (id: string) => Promise<ProcessingActivity>
|
||||
|
||||
// Actions - Vendors
|
||||
createVendor: (data: Omit<Vendor, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>) => Promise<Vendor>
|
||||
updateVendor: (id: string, data: Partial<Vendor>) => Promise<void>
|
||||
deleteVendor: (id: string) => Promise<void>
|
||||
|
||||
// Actions - Contracts
|
||||
uploadContract: (vendorId: string, file: File, metadata: Partial<ContractDocument>) => Promise<ContractDocument>
|
||||
updateContract: (id: string, data: Partial<ContractDocument>) => Promise<void>
|
||||
deleteContract: (id: string) => Promise<void>
|
||||
startContractReview: (contractId: string) => Promise<void>
|
||||
|
||||
// Actions - Findings
|
||||
updateFinding: (id: string, data: Partial<Finding>) => Promise<void>
|
||||
resolveFinding: (id: string, resolution: string) => Promise<void>
|
||||
|
||||
// Actions - Controls
|
||||
updateControlInstance: (id: string, data: Partial<ControlInstance>) => Promise<void>
|
||||
|
||||
// Actions - Export
|
||||
exportVVT: (format: ExportFormat, activityIds?: string[]) => Promise<string>
|
||||
exportVendorAuditPack: (vendorId: string, format: ExportFormat) => Promise<string>
|
||||
exportRoPA: (format: ExportFormat) => Promise<string>
|
||||
|
||||
// Data Loading
|
||||
loadData: () => Promise<void>
|
||||
refresh: () => Promise<void>
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// API RESPONSE TYPES
|
||||
// ==========================================
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
success: boolean
|
||||
data?: T
|
||||
error?: string
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
|
||||
pagination: {
|
||||
page: number
|
||||
pageSize: number
|
||||
total: number
|
||||
totalPages: number
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// FORM TYPES
|
||||
// ==========================================
|
||||
|
||||
export interface ProcessingActivityFormData {
|
||||
vvtId: string
|
||||
name: LocalizedText
|
||||
responsible: ResponsibleParty
|
||||
dpoContact?: Contact
|
||||
purposes: LocalizedText[]
|
||||
dataSubjectCategories: DataSubjectCategory[]
|
||||
personalDataCategories: PersonalDataCategory[]
|
||||
recipientCategories: RecipientCategory[]
|
||||
thirdCountryTransfers: ThirdCountryTransfer[]
|
||||
retentionPeriod: RetentionPeriod
|
||||
technicalMeasures: string[]
|
||||
legalBasis: LegalBasis[]
|
||||
dataSources: DataSource[]
|
||||
systems: SystemReference[]
|
||||
dataFlows: DataFlow[]
|
||||
protectionLevel: ProtectionLevel
|
||||
dpiaRequired: boolean
|
||||
dpiaJustification?: string
|
||||
subProcessors: string[]
|
||||
owner: string
|
||||
}
|
||||
|
||||
export interface VendorFormData {
|
||||
name: string
|
||||
legalForm?: string
|
||||
country: string
|
||||
address: Address
|
||||
website?: string
|
||||
role: VendorRole
|
||||
serviceDescription: string
|
||||
serviceCategory: ServiceCategory
|
||||
dataAccessLevel: DataAccessLevel
|
||||
processingLocations: ProcessingLocation[]
|
||||
transferMechanisms: TransferMechanismType[]
|
||||
certifications: Certification[]
|
||||
primaryContact: Contact
|
||||
dpoContact?: Contact
|
||||
securityContact?: Contact
|
||||
contractTypes: DocumentType[]
|
||||
reviewFrequency: ReviewFrequency
|
||||
notes?: string
|
||||
}
|
||||
146
admin-lehrer/lib/sdk/vendor-compliance/types-vendor.ts
Normal file
146
admin-lehrer/lib/sdk/vendor-compliance/types-vendor.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Vendor Types
|
||||
*
|
||||
* Types for vendor/supplier management:
|
||||
* - Vendor roles, statuses, service categories
|
||||
* - Processing locations, certifications
|
||||
* - Vendor interface with risk scoring
|
||||
*/
|
||||
|
||||
import type { Address, Contact } from './types-common'
|
||||
import type { DocumentType } from './types-contract'
|
||||
import type { TransferMechanismType } from './types-processing'
|
||||
|
||||
// ==========================================
|
||||
// ENUMS - VENDOR
|
||||
// ==========================================
|
||||
|
||||
export type VendorRole =
|
||||
| 'PROCESSOR' // Auftragsverarbeiter
|
||||
| 'JOINT_CONTROLLER' // Gemeinsam Verantwortlicher
|
||||
| 'CONTROLLER' // Eigenstaendiger Verantwortlicher
|
||||
| 'SUB_PROCESSOR' // Unterauftragnehmer
|
||||
| 'THIRD_PARTY' // Dritter (kein Datenzugriff)
|
||||
|
||||
export type ServiceCategory =
|
||||
| 'HOSTING'
|
||||
| 'CLOUD_INFRASTRUCTURE'
|
||||
| 'ANALYTICS'
|
||||
| 'CRM'
|
||||
| 'ERP'
|
||||
| 'HR_SOFTWARE'
|
||||
| 'PAYMENT'
|
||||
| 'EMAIL'
|
||||
| 'MARKETING'
|
||||
| 'SUPPORT'
|
||||
| 'SECURITY'
|
||||
| 'INTEGRATION'
|
||||
| 'CONSULTING'
|
||||
| 'LEGAL'
|
||||
| 'ACCOUNTING'
|
||||
| 'COMMUNICATION'
|
||||
| 'STORAGE'
|
||||
| 'BACKUP'
|
||||
| 'CDN'
|
||||
| 'OTHER'
|
||||
|
||||
export type DataAccessLevel =
|
||||
| 'NONE' // Kein Datenzugriff
|
||||
| 'POTENTIAL' // Potenzieller Zugriff (z.B. Admin)
|
||||
| 'ADMINISTRATIVE' // Administrativer Zugriff
|
||||
| 'CONTENT' // Inhaltlicher Zugriff
|
||||
|
||||
export type VendorStatus =
|
||||
| 'ACTIVE'
|
||||
| 'INACTIVE'
|
||||
| 'PENDING_REVIEW'
|
||||
| 'TERMINATED'
|
||||
|
||||
export type ReviewFrequency =
|
||||
| 'QUARTERLY'
|
||||
| 'SEMI_ANNUAL'
|
||||
| 'ANNUAL'
|
||||
| 'BIENNIAL'
|
||||
|
||||
// ==========================================
|
||||
// INTERFACES - VENDOR
|
||||
// ==========================================
|
||||
|
||||
export interface ProcessingLocation {
|
||||
country: string // ISO 3166-1 alpha-2
|
||||
region?: string
|
||||
city?: string
|
||||
dataCenter?: string
|
||||
isEU: boolean
|
||||
isAdequate: boolean // Angemessenheitsbeschluss
|
||||
type?: string // e.g., 'primary', 'backup', 'disaster-recovery'
|
||||
description?: string
|
||||
isPrimary?: boolean
|
||||
}
|
||||
|
||||
export interface Certification {
|
||||
type: string // ISO 27001, SOC2, TISAX, C5, etc.
|
||||
issuer?: string
|
||||
issuedDate?: Date
|
||||
expirationDate?: Date
|
||||
scope?: string
|
||||
certificateNumber?: string
|
||||
documentId?: string // Referenz zum hochgeladenen Zertifikat
|
||||
}
|
||||
|
||||
export interface Vendor {
|
||||
id: string
|
||||
tenantId: string
|
||||
|
||||
// Stammdaten
|
||||
name: string
|
||||
legalForm?: string
|
||||
country: string
|
||||
address: Address
|
||||
website?: string
|
||||
|
||||
// Rolle
|
||||
role: VendorRole
|
||||
serviceDescription: string
|
||||
serviceCategory: ServiceCategory
|
||||
|
||||
// Datenzugriff
|
||||
dataAccessLevel: DataAccessLevel
|
||||
processingLocations: ProcessingLocation[]
|
||||
transferMechanisms: TransferMechanismType[]
|
||||
|
||||
// Zertifizierungen
|
||||
certifications: Certification[]
|
||||
|
||||
// Kontakte
|
||||
primaryContact: Contact
|
||||
dpoContact?: Contact
|
||||
securityContact?: Contact
|
||||
|
||||
// Vertraege
|
||||
contractTypes: DocumentType[]
|
||||
contracts: string[] // Contract-IDs
|
||||
|
||||
// Risiko
|
||||
inherentRiskScore: number // 0-100 (auto-berechnet)
|
||||
residualRiskScore: number // 0-100 (nach Controls)
|
||||
manualRiskAdjustment?: number
|
||||
riskJustification?: string
|
||||
|
||||
// Review
|
||||
reviewFrequency: ReviewFrequency
|
||||
lastReviewDate?: Date
|
||||
nextReviewDate?: Date
|
||||
|
||||
// Workflow
|
||||
status: VendorStatus
|
||||
|
||||
// Linked Processing Activities
|
||||
processingActivityIds: string[]
|
||||
|
||||
// Notes
|
||||
notes?: string
|
||||
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user