feat(sdk): Multi-Tenancy, Versionierung, Change-Requests, Dokumentengenerierung (Phase 1-6)
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 30s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 18s

6-Phasen-Implementation fuer cloud-faehiges, mandantenfaehiges Compliance SDK:

Phase 1: Multi-Tenancy Fix
- Shared tenant_utils.py Dependency (UUID-Validierung, kein "default" mehr)
- VVT tenant_id Column + tenant-scoped Queries
- DSFA/Vendor DEFAULT_TENANT_ID von "default" auf UUID migriert
- Migration 035

Phase 2: Stammdaten-Erweiterung
- Company Profile um JSONB-Felder erweitert (processing_systems, ai_systems, technical_contacts)
- Regulierungs-Flags (NIS2, AI Act, ISO 27001)
- GET /template-context Endpoint
- Migration 036

Phase 3: Dokument-Versionierung
- 5 Versions-Tabellen (DSFA, VVT, TOM, Loeschfristen, Obligations)
- Shared versioning_utils.py Helper
- /{id}/versions Endpoints auf allen 5 Dokumenttypen
- Migration 037

Phase 4: Change-Request System
- Zentrale CR-Inbox mit CRUD + Accept/Reject/Edit Workflow
- Regelbasierte CR-Engine (VVT DPIA → DSFA CR, Datenkategorien → Loeschfristen CR)
- Audit-Trail
- Migration 038

Phase 5: Dokumentengenerierung
- 5 Template-Generatoren (DSFA, VVT, TOM, Loeschfristen, Obligations)
- Preview + Apply Endpoints (erzeugt CRs, keine direkten Dokumente)

Phase 6: Frontend-Integration
- Change-Request Inbox Page mit Stats, Filtern, Modals
- VersionHistory Timeline-Komponente
- SDKSidebar CR-Badge (60s Polling)
- Company Profile: 2 neue Wizard-Steps + "Dokumente generieren" CTA

Docs: 5 neue MkDocs-Seiten, CLAUDE.md aktualisiert
Tests: 97 neue Tests (alle bestanden)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-07 14:12:34 +01:00
parent ef9aed666f
commit 1e84df9769
41 changed files with 4818 additions and 52 deletions

View File

@@ -35,9 +35,11 @@ const BASE_WIZARD_STEPS = [
{ id: 3, name: 'Firmengroesse', description: 'Mitarbeiter und Umsatz' },
{ id: 4, name: 'Standorte', description: 'Hauptsitz und Zielmaerkte' },
{ id: 5, name: 'Datenschutz', description: 'Rollen und KI-Nutzung' },
{ id: 6, name: 'Systeme & KI', description: 'IT-Systeme und KI-Katalog' },
{ id: 7, name: 'Rechtlicher Rahmen', description: 'Regulierungen und Prüfzyklen' },
]
const MACHINE_BUILDER_STEP = { id: 6, name: 'Produkt & Maschine', description: 'Software, KI und CE in Ihrem Produkt' }
const MACHINE_BUILDER_STEP = { id: 8, name: 'Produkt & Maschine', description: 'Software, KI und CE in Ihrem Produkt' }
function getWizardSteps(industry: string) {
if (isMachineBuilderIndustry(industry)) {
@@ -542,7 +544,277 @@ function StepDataProtection({
}
// =============================================================================
// STEP 6: PRODUKT & MASCHINE (nur fuer Maschinenbauer)
// STEP 6: SYSTEME & KI
// =============================================================================
interface ProcessingSystem {
name: string
vendor: string
hosting: string
personal_data_categories: string[]
}
interface AISystem {
name: string
purpose: string
risk_category: string
vendor: string
has_human_oversight: boolean
}
function StepSystemsAndAI({
data,
onChange,
}: {
data: Partial<CompanyProfile> & { processingSystems?: ProcessingSystem[]; aiSystems?: AISystem[] }
onChange: (updates: Record<string, unknown>) => void
}) {
const systems = (data as any).processingSystems || []
const aiSystems = (data as any).aiSystems || []
const addSystem = () => {
onChange({ processingSystems: [...systems, { name: '', vendor: '', hosting: 'cloud', personal_data_categories: [] }] })
}
const removeSystem = (i: number) => {
onChange({ processingSystems: systems.filter((_: ProcessingSystem, idx: number) => idx !== i) })
}
const updateSystem = (i: number, updates: Partial<ProcessingSystem>) => {
const updated = [...systems]
updated[i] = { ...updated[i], ...updates }
onChange({ processingSystems: updated })
}
const addAISystem = () => {
onChange({ aiSystems: [...aiSystems, { name: '', purpose: '', risk_category: 'limited', vendor: '', has_human_oversight: true }] })
}
const removeAISystem = (i: number) => {
onChange({ aiSystems: aiSystems.filter((_: AISystem, idx: number) => idx !== i) })
}
const updateAISystem = (i: number, updates: Partial<AISystem>) => {
const updated = [...aiSystems]
updated[i] = { ...updated[i], ...updates }
onChange({ aiSystems: updated })
}
return (
<div className="space-y-8">
{/* Processing Systems */}
<div>
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-sm font-medium text-gray-700">IT-Systeme mit personenbezogenen Daten</h3>
<p className="text-xs text-gray-500">Systeme, die personenbezogene Daten verarbeiten (fuer VVT-Generierung)</p>
</div>
<button type="button" onClick={addSystem} className="px-3 py-1.5 text-sm bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200">
+ System
</button>
</div>
{systems.length === 0 && (
<div className="text-center py-6 text-gray-400 border-2 border-dashed rounded-lg">Noch keine Systeme hinzugefuegt</div>
)}
<div className="space-y-3">
{systems.map((sys: ProcessingSystem, i: number) => (
<div key={i} className="border border-gray-200 rounded-lg p-4 space-y-3">
<div className="flex justify-between items-center">
<span className="text-xs font-medium text-gray-400">System {i + 1}</span>
<button type="button" onClick={() => removeSystem(i)} className="text-red-400 hover:text-red-600 text-xs">Entfernen</button>
</div>
<div className="grid grid-cols-2 gap-3">
<input type="text" value={sys.name} onChange={e => updateSystem(i, { name: e.target.value })} placeholder="Name (z.B. SAP HR)" className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
<input type="text" value={sys.vendor} onChange={e => updateSystem(i, { vendor: e.target.value })} placeholder="Hersteller" className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
</div>
<div className="grid grid-cols-2 gap-3">
<select value={sys.hosting} onChange={e => updateSystem(i, { hosting: e.target.value })} className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent">
<option value="on-premise">On-Premise</option>
<option value="cloud">Cloud (EU)</option>
<option value="us-cloud">Cloud (US)</option>
<option value="hybrid">Hybrid</option>
</select>
<input type="text" value={sys.personal_data_categories.join(', ')} onChange={e => updateSystem(i, { personal_data_categories: e.target.value.split(',').map(s => s.trim()).filter(Boolean) })} placeholder="Datenkategorien (kommagetrennt)" className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
</div>
</div>
))}
</div>
</div>
{/* AI Systems */}
<div className="border-t border-gray-200 pt-6">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-sm font-medium text-gray-700">KI-Systeme</h3>
<p className="text-xs text-gray-500">Strukturierter KI-Katalog fuer AI Act Compliance</p>
</div>
<button type="button" onClick={addAISystem} className="px-3 py-1.5 text-sm bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200">
+ KI-System
</button>
</div>
{aiSystems.length === 0 && (
<div className="text-center py-6 text-gray-400 border-2 border-dashed rounded-lg">Noch keine KI-Systeme</div>
)}
<div className="space-y-3">
{aiSystems.map((ai: AISystem, i: number) => (
<div key={i} className="border border-gray-200 rounded-lg p-4 space-y-3">
<div className="flex justify-between items-center">
<span className="text-xs font-medium text-gray-400">KI-System {i + 1}</span>
<button type="button" onClick={() => removeAISystem(i)} className="text-red-400 hover:text-red-600 text-xs">Entfernen</button>
</div>
<div className="grid grid-cols-2 gap-3">
<input type="text" value={ai.name} onChange={e => updateAISystem(i, { name: e.target.value })} placeholder="Name (z.B. Chatbot)" className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
<input type="text" value={ai.vendor} onChange={e => updateAISystem(i, { vendor: e.target.value })} placeholder="Anbieter" className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
</div>
<input type="text" value={ai.purpose} onChange={e => updateAISystem(i, { purpose: e.target.value })} placeholder="Zweck (z.B. Kundensupport)" className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
<div className="grid grid-cols-2 gap-3">
<select value={ai.risk_category} onChange={e => updateAISystem(i, { risk_category: e.target.value })} className="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent">
<option value="minimal">Minimal Risk</option>
<option value="limited">Limited Risk</option>
<option value="high">High Risk</option>
<option value="unacceptable">Unacceptable Risk</option>
</select>
<label className="flex items-center gap-2 px-3 py-2">
<input type="checkbox" checked={ai.has_human_oversight} onChange={e => updateAISystem(i, { has_human_oversight: e.target.checked })} className="w-4 h-4 text-purple-600 rounded focus:ring-purple-500" />
<span className="text-sm text-gray-700">Human Oversight</span>
</label>
</div>
</div>
))}
</div>
</div>
</div>
)
}
// =============================================================================
// STEP 7: RECHTLICHER RAHMEN
// =============================================================================
function StepLegalFramework({
data,
onChange,
}: {
data: Partial<CompanyProfile> & { subjectToNis2?: boolean; subjectToAiAct?: boolean; subjectToIso27001?: boolean; supervisoryAuthority?: string; reviewCycleMonths?: number; technicalContacts?: { name: string; role: string; email: string }[] }
onChange: (updates: Record<string, unknown>) => void
}) {
const contacts = (data as any).technicalContacts || []
const addContact = () => {
onChange({ technicalContacts: [...contacts, { name: '', role: '', email: '' }] })
}
const removeContact = (i: number) => {
onChange({ technicalContacts: contacts.filter((_: { name: string; role: string; email: string }, idx: number) => idx !== i) })
}
const updateContact = (i: number, updates: Partial<{ name: string; role: string; email: string }>) => {
const updated = [...contacts]
updated[i] = { ...updated[i], ...updates }
onChange({ technicalContacts: updated })
}
return (
<div className="space-y-8">
{/* Regulatory Flags */}
<div>
<h3 className="text-sm font-medium text-gray-700 mb-4">Regulatorischer Rahmen</h3>
<div className="space-y-3">
{[
{ key: 'subjectToNis2', label: 'NIS2-Richtlinie', desc: 'Ihr Unternehmen fällt unter die NIS2-Richtlinie (Netzwerk- und Informationssicherheit)' },
{ key: 'subjectToAiAct', label: 'EU AI Act', desc: 'Ihr Unternehmen setzt KI-Systeme ein, die unter den AI Act fallen' },
{ key: 'subjectToIso27001', label: 'ISO 27001', desc: 'Ihr Unternehmen strebt ISO 27001 Zertifizierung an oder ist bereits zertifiziert' },
].map(item => (
<label
key={item.key}
className={`flex items-start gap-4 p-4 rounded-xl border-2 cursor-pointer transition-all ${
(data as any)[item.key] ? 'border-purple-500 bg-purple-50' : 'border-gray-200 hover:border-purple-300'
}`}
>
<input
type="checkbox"
checked={(data as any)[item.key] ?? false}
onChange={e => onChange({ [item.key]: e.target.checked })}
className="mt-1 w-5 h-5 text-purple-600 rounded focus:ring-purple-500"
/>
<div>
<div className="font-medium text-gray-900">{item.label}</div>
<div className="text-sm text-gray-500">{item.desc}</div>
</div>
</label>
))}
</div>
</div>
{/* Supervisory Authority & Review Cycle */}
<div className="grid grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Aufsichtsbehörde</label>
<select
value={(data as any).supervisoryAuthority || ''}
onChange={e => onChange({ supervisoryAuthority: e.target.value })}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
>
<option value="">Bitte wählen...</option>
<option value="LfDI BW">LfDI Baden-Württemberg</option>
<option value="BayLDA">BayLDA Bayern</option>
<option value="BlnBDI">BlnBDI Berlin</option>
<option value="LDA BB">LDA Brandenburg</option>
<option value="LfDI HB">LfDI Bremen</option>
<option value="HmbBfDI">HmbBfDI Hamburg</option>
<option value="HBDI">HBDI Hessen</option>
<option value="LfDI MV">LfDI Mecklenburg-Vorpommern</option>
<option value="LfD NI">LfD Niedersachsen</option>
<option value="LDI NRW">LDI NRW</option>
<option value="LfDI RP">LfDI Rheinland-Pfalz</option>
<option value="UDZ SL">UDZ Saarland</option>
<option value="SächsDSB">Sächsischer DSB</option>
<option value="LfD LSA">LfD Sachsen-Anhalt</option>
<option value="ULD SH">ULD Schleswig-Holstein</option>
<option value="TLfDI">TLfDI Thüringen</option>
<option value="BfDI">BfDI (Bund)</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Prüfzyklus (Monate)</label>
<select
value={(data as any).reviewCycleMonths || 12}
onChange={e => onChange({ reviewCycleMonths: parseInt(e.target.value) })}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
>
<option value={3}>Vierteljährlich (3 Monate)</option>
<option value={6}>Halbjährlich (6 Monate)</option>
<option value={12}>Jährlich (12 Monate)</option>
<option value={24}>Zweijährlich (24 Monate)</option>
</select>
</div>
</div>
{/* Technical Contacts */}
<div className="border-t border-gray-200 pt-6">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-sm font-medium text-gray-700">Technische Ansprechpartner</h3>
<p className="text-xs text-gray-500">CISO, IT-Manager, DSB etc.</p>
</div>
<button type="button" onClick={addContact} className="px-3 py-1.5 text-sm bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200">
+ Kontakt
</button>
</div>
{contacts.length === 0 && (
<div className="text-center py-4 text-gray-400 border-2 border-dashed rounded-lg text-sm">Noch keine Kontakte</div>
)}
<div className="space-y-3">
{contacts.map((c: { name: string; role: string; email: string }, i: number) => (
<div key={i} className="flex gap-3 items-center">
<input type="text" value={c.name} onChange={e => updateContact(i, { name: e.target.value })} placeholder="Name" className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
<input type="text" value={c.role} onChange={e => updateContact(i, { role: e.target.value })} placeholder="Rolle (z.B. CISO)" className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
<input type="email" value={c.email} onChange={e => updateContact(i, { email: e.target.value })} placeholder="E-Mail" className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
<button type="button" onClick={() => removeContact(i)} className="text-red-400 hover:text-red-600 text-sm">X</button>
</div>
))}
</div>
</div>
</div>
)
}
// =============================================================================
// STEP 8: PRODUKT & MASCHINE (nur fuer Maschinenbauer)
// =============================================================================
const EMPTY_MACHINE_BUILDER: MachineBuilderProfile = {
@@ -1090,6 +1362,71 @@ function CoverageAssessmentPanel({ profile }: { profile: Partial<CompanyProfile>
)
}
// =============================================================================
// GENERATE DOCUMENTS BUTTON
// =============================================================================
const DOC_TYPES = [
{ id: 'dsfa', label: 'DSFA', desc: 'Datenschutz-Folgenabschätzung' },
{ id: 'vvt', label: 'VVT', desc: 'Verarbeitungsverzeichnis' },
{ id: 'tom', label: 'TOM', desc: 'Technisch-Organisatorische Maßnahmen' },
{ id: 'loeschfristen', label: 'Löschfristen', desc: 'Löschfristen-Katalog' },
{ id: 'obligation', label: 'Pflichten', desc: 'Compliance-Pflichten' },
]
function GenerateDocumentsButton() {
const [generating, setGenerating] = useState<string | null>(null)
const [results, setResults] = useState<Record<string, { ok: boolean; count: number }>>({})
const handleGenerate = async (docType: string) => {
setGenerating(docType)
try {
const res = await fetch(`/api/sdk/v1/compliance/generation/apply/${docType}`, { method: 'POST' })
if (res.ok) {
const data = await res.json()
setResults(prev => ({ ...prev, [docType]: { ok: true, count: data.change_requests_created || 0 } }))
} else {
setResults(prev => ({ ...prev, [docType]: { ok: false, count: 0 } }))
}
} catch {
setResults(prev => ({ ...prev, [docType]: { ok: false, count: 0 } }))
} finally {
setGenerating(null)
}
}
return (
<div className="space-y-2">
{DOC_TYPES.map(dt => (
<div key={dt.id} className="flex items-center justify-between">
<div>
<span className="text-sm font-medium text-gray-900">{dt.label}</span>
<span className="text-xs text-gray-500 ml-1">({dt.desc})</span>
</div>
{results[dt.id] ? (
<span className={`text-xs px-2 py-1 rounded ${results[dt.id].ok ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
{results[dt.id].ok ? `${results[dt.id].count} CR erstellt` : 'Fehler'}
</span>
) : (
<button
onClick={() => handleGenerate(dt.id)}
disabled={generating !== null}
className="px-3 py-1 text-xs bg-purple-600 text-white rounded hover:bg-purple-700 disabled:opacity-50"
>
{generating === dt.id ? 'Generiere...' : 'Generieren'}
</button>
)}
</div>
))}
{Object.keys(results).length > 0 && (
<a href="/sdk/change-requests" className="block text-center text-sm text-purple-600 hover:text-purple-800 font-medium mt-3">
Zur Änderungsanfragen-Inbox &rarr;
</a>
)}
</div>
)
}
// =============================================================================
// MAIN COMPONENT
// =============================================================================
@@ -1165,10 +1502,21 @@ export default function CompanyProfilePage() {
dpoName: data.dpo_name || '',
dpoEmail: data.dpo_email || '',
isComplete: data.is_complete || false,
}
// Phase 2 extended fields
processingSystems: data.processing_systems || [],
aiSystems: data.ai_systems || [],
technicalContacts: data.technical_contacts || [],
subjectToNis2: data.subject_to_nis2 || false,
subjectToAiAct: data.subject_to_ai_act || false,
subjectToIso27001: data.subject_to_iso27001 || false,
supervisoryAuthority: data.supervisory_authority || '',
reviewCycleMonths: data.review_cycle_months || 12,
repos: data.repos || [],
documentSources: data.document_sources || [],
} as any
setFormData(backendProfile)
if (backendProfile.isComplete) {
setCurrentStep(5)
setCurrentStep(7)
}
return
}
@@ -1181,7 +1529,7 @@ export default function CompanyProfilePage() {
if (!cancelled && state.companyProfile) {
setFormData(state.companyProfile)
if (state.companyProfile.isComplete) {
setCurrentStep(5)
setCurrentStep(7)
}
}
}
@@ -1198,10 +1546,10 @@ export default function CompanyProfilePage() {
const handleNext = () => {
if (currentStep < lastStep) {
// Skip step 6 if not a machine builder
// Skip step 8 if not a machine builder
const nextStep = currentStep + 1
if (nextStep === 6 && !showMachineBuilderStep) {
// Complete profile (was step 5, last step for non-machine-builders)
if (nextStep === 8 && !showMachineBuilderStep) {
// Complete profile (was step 7, last step for non-machine-builders)
completeAndSaveProfile()
return
}
@@ -1250,6 +1598,17 @@ export default function CompanyProfilePage() {
dpo_name: formData.dpoName || '',
dpo_email: formData.dpoEmail || '',
is_complete: true,
// Phase 2 extended fields
processing_systems: (formData as any).processingSystems || [],
ai_systems: (formData as any).aiSystems || [],
technical_contacts: (formData as any).technicalContacts || [],
subject_to_nis2: (formData as any).subjectToNis2 || false,
subject_to_ai_act: (formData as any).subjectToAiAct || false,
subject_to_iso27001: (formData as any).subjectToIso27001 || false,
supervisory_authority: (formData as any).supervisoryAuthority || '',
review_cycle_months: (formData as any).reviewCycleMonths || 12,
repos: (formData as any).repos || [],
document_sources: (formData as any).documentSources || [],
// Machine builder fields (if applicable)
...(formData.machineBuilder ? {
machine_builder: {
@@ -1351,6 +1710,10 @@ export default function CompanyProfilePage() {
case 5:
return true
case 6:
return true // Systems & AI step is optional
case 7:
return true // Legal framework step is optional
case 8:
// Machine builder step: require at least product description
return (formData.machineBuilder?.productDescription?.length || 0) > 0
default:
@@ -1358,7 +1721,7 @@ export default function CompanyProfilePage() {
}
}
const isLastStep = currentStep === lastStep || (currentStep === 5 && !showMachineBuilderStep)
const isLastStep = currentStep === lastStep || (currentStep === 7 && !showMachineBuilderStep)
return (
<div className="min-h-screen bg-gray-50 py-8">
@@ -1438,7 +1801,9 @@ export default function CompanyProfilePage() {
{currentStep === 3 && <StepCompanySize data={formData} onChange={updateFormData} />}
{currentStep === 4 && <StepLocations data={formData} onChange={updateFormData} />}
{currentStep === 5 && <StepDataProtection data={formData} onChange={updateFormData} />}
{currentStep === 6 && showMachineBuilderStep && <StepMachineBuilder data={formData} onChange={updateFormData} />}
{currentStep === 6 && <StepSystemsAndAI data={formData} onChange={updateFormData} />}
{currentStep === 7 && <StepLegalFramework data={formData} onChange={updateFormData} />}
{currentStep === 8 && showMachineBuilderStep && <StepMachineBuilder data={formData} onChange={updateFormData} />}
{/* Navigation */}
<div className="flex justify-between mt-8 pt-6 border-t border-gray-200">
@@ -1517,6 +1882,17 @@ export default function CompanyProfilePage() {
</div>
</div>
</div>
{/* Generate Documents CTA */}
{formData.isComplete && (
<div className="mt-6 bg-gradient-to-br from-purple-50 to-indigo-50 rounded-xl border border-purple-200 p-6">
<h3 className="font-semibold text-purple-900 mb-2">Dokumente generieren</h3>
<p className="text-sm text-purple-700 mb-4">
Basierend auf Ihrem Profil können DSFA, VVT, TOM, Löschfristen und Pflichten automatisch als Entwürfe generiert werden.
</p>
<GenerateDocumentsButton />
</div>
)}
{/* Delete Profile Button */}
{formData.companyName && (
<div className="mt-6">