// ============================================================================= // Obligations Module - Pflichtenregister Document Generator // Generates a printable, audit-ready HTML document for the obligation register // ============================================================================= import type { Obligation, ObligationComplianceCheckResult, ObligationComplianceIssueSeverity } from './obligations-compliance' import { OBLIGATION_SEVERITY_LABELS_DE, OBLIGATION_SEVERITY_COLORS } from './obligations-compliance' // ============================================================================= // TYPES // ============================================================================= export interface ObligationDocumentOrgHeader { organizationName: string industry: string dpoName: string dpoContact: string responsiblePerson: string legalDepartment: string documentVersion: string lastReviewDate: string nextReviewDate: string reviewInterval: string } export interface ObligationDocumentRevision { version: string date: string author: string changes: string } // ============================================================================= // DEFAULTS // ============================================================================= export function createDefaultObligationDocumentOrgHeader(): ObligationDocumentOrgHeader { const now = new Date() const nextYear = new Date() nextYear.setFullYear(nextYear.getFullYear() + 1) return { organizationName: '', industry: '', dpoName: '', dpoContact: '', responsiblePerson: '', legalDepartment: '', documentVersion: '1.0', lastReviewDate: now.toISOString().split('T')[0], nextReviewDate: nextYear.toISOString().split('T')[0], reviewInterval: 'Jaehrlich', } } // ============================================================================= // STATUS & PRIORITY LABELS // ============================================================================= const STATUS_LABELS_DE: Record = { 'pending': 'Ausstehend', 'in-progress': 'In Bearbeitung', 'completed': 'Abgeschlossen', 'overdue': 'Ueberfaellig', } const STATUS_BADGE_CLASSES: Record = { 'pending': 'badge-draft', 'in-progress': 'badge-review', 'completed': 'badge-active', 'overdue': 'badge-critical', } const PRIORITY_LABELS_DE: Record = { critical: 'Kritisch', high: 'Hoch', medium: 'Mittel', low: 'Niedrig', } const PRIORITY_BADGE_CLASSES: Record = { critical: 'badge-critical', high: 'badge-high', medium: 'badge-medium', low: 'badge-low', } // ============================================================================= // HTML DOCUMENT BUILDER // ============================================================================= export function buildObligationDocumentHtml( obligations: Obligation[], orgHeader: ObligationDocumentOrgHeader, complianceResult: ObligationComplianceCheckResult | null, revisions: ObligationDocumentRevision[] ): string { const today = new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', }) const orgName = orgHeader.organizationName || 'Organisation' // Group obligations by source (regulation) const bySource = new Map() for (const o of obligations) { const src = o.source || 'Sonstig' if (!bySource.has(src)) bySource.set(src, []) bySource.get(src)!.push(o) } // Build role map const roleMap = new Map() for (const o of obligations) { const role = o.responsible || 'Nicht zugewiesen' if (!roleMap.has(role)) roleMap.set(role, []) roleMap.get(role)!.push(o) } // Distinct sources const distinctSources = Array.from(bySource.keys()).sort() // ========================================================================= // HTML Template // ========================================================================= let html = ` Pflichtenregister — ${escHtml(orgName)} ` // ========================================================================= // Section 0: Cover Page // ========================================================================= html += `

Pflichtenregister

Regulatorische Pflichten — DSGVO, AI Act, NIS2 und weitere
Organisation: ${escHtml(orgName)}
${orgHeader.industry ? `
Branche: ${escHtml(orgHeader.industry)}
` : ''} ${orgHeader.dpoName ? `
DSB: ${escHtml(orgHeader.dpoName)}
` : ''} ${orgHeader.dpoContact ? `
DSB-Kontakt: ${escHtml(orgHeader.dpoContact)}
` : ''} ${orgHeader.responsiblePerson ? `
Verantwortlicher: ${escHtml(orgHeader.responsiblePerson)}
` : ''} ${orgHeader.legalDepartment ? `
Rechtsabteilung: ${escHtml(orgHeader.legalDepartment)}
` : ''}
` // ========================================================================= // Table of Contents // ========================================================================= const sections = [ 'Ziel und Zweck', 'Geltungsbereich', 'Methodik', 'Regulatorische Grundlagen', 'Pflichtenuebersicht', 'Detaillierte Pflichten', 'Verantwortlichkeiten', 'Fristen und Termine', 'Nachweisverzeichnis', 'Compliance-Status', 'Aenderungshistorie', ] html += `

Inhaltsverzeichnis

${sections.map((s, i) => `
${i + 1}. ${escHtml(s)}
`).join('\n ')}
` // ========================================================================= // Section 1: Ziel und Zweck // ========================================================================= html += `
1. Ziel und Zweck

Dieses Pflichtenregister dokumentiert alle regulatorischen Pflichten, denen ${escHtml(orgName)} unterliegt. Es dient der systematischen Erfassung, Ueberwachung und Nachverfolgung aller Compliance-Anforderungen aus den anwendbaren Regulierungen.

Das Register erfuellt folgende Zwecke:

  • Vollstaendige Erfassung aller anwendbaren regulatorischen Pflichten
  • Zuordnung von Verantwortlichkeiten und Fristen
  • Nachverfolgung des Umsetzungsstatus
  • Dokumentation von Nachweisen fuer Audits
  • Identifikation von Compliance-Luecken und Handlungsbedarf
RechtsrahmenRelevanz
DSGVO (EU) 2016/679Datenschutz-Grundverordnung — Kernregulierung fuer personenbezogene Daten
AI Act (EU) 2024/1689KI-Verordnung — Anforderungen an KI-Systeme nach Risikoklasse
NIS2 (EU) 2022/2555Netzwerk- und Informationssicherheit — Cybersicherheitspflichten
BDSGBundesdatenschutzgesetz — Nationale Ergaenzung zur DSGVO
` // ========================================================================= // Section 2: Geltungsbereich // ========================================================================= html += `
2. Geltungsbereich

Dieses Pflichtenregister gilt fuer alle Geschaeftsprozesse und IT-Systeme von ${escHtml(orgName)}${orgHeader.industry ? ` (Branche: ${escHtml(orgHeader.industry)})` : ''}.

Anwendbare Regulierungen:

` for (const [source, obls] of bySource.entries()) { const completed = obls.filter(o => o.status === 'completed').length const pct = obls.length > 0 ? Math.round((completed / obls.length) * 100) : 0 html += ` ` } html += `
RegulierungAnzahl PflichtenStatus
${escHtml(source)} ${obls.length} ${completed}/${obls.length} abgeschlossen (${pct}%)

Insgesamt umfasst dieses Register ${obligations.length} Pflichten aus ${distinctSources.length} Regulierungen.

` // ========================================================================= // Section 3: Methodik // ========================================================================= html += `
3. Methodik

Die Identifikation und Bewertung der Pflichten erfolgt in drei Schritten:

Pflicht-Identifikation: Systematische Analyse aller anwendbaren Regulierungen und Extraktion der einzelnen Pflichten mit Artikel-Referenz, Beschreibung und Zielgruppe.
Bewertung und Priorisierung: Jede Pflicht wird nach Prioritaet (kritisch, hoch, mittel, niedrig) und Dringlichkeit (Frist) bewertet. Die Bewertung basiert auf dem Risikopotenzial bei Nichterfuellung.
Ueberwachung und Nachverfolgung: Regelmaessige Pruefung des Umsetzungsstatus, Aktualisierung der Fristen und Dokumentation von Nachweisen.

Die Pflichten werden ueber einen automatisierten Compliance-Check geprueft, der 11 Kriterien umfasst (siehe Abschnitt 10: Compliance-Status).

` // ========================================================================= // Section 4: Regulatorische Grundlagen // ========================================================================= html += `
4. Regulatorische Grundlagen

Die folgende Tabelle zeigt die regulatorischen Grundlagen mit Artikelzahl und Umsetzungsstatus:

` for (const [source, obls] of bySource.entries()) { const critical = obls.filter(o => o.priority === 'critical').length const high = obls.filter(o => o.priority === 'high').length const medium = obls.filter(o => o.priority === 'medium').length const low = obls.filter(o => o.priority === 'low').length const completed = obls.filter(o => o.status === 'completed').length html += ` ` } // Totals row const totalCritical = obligations.filter(o => o.priority === 'critical').length const totalHigh = obligations.filter(o => o.priority === 'high').length const totalMedium = obligations.filter(o => o.priority === 'medium').length const totalLow = obligations.filter(o => o.priority === 'low').length const totalCompleted = obligations.filter(o => o.status === 'completed').length html += ` ` html += `
Regulierung Pflichten Kritisch Hoch Mittel Niedrig Abgeschlossen
${escHtml(source)} ${obls.length} ${critical} ${high} ${medium} ${low} ${completed}
Gesamt ${obligations.length} ${totalCritical} ${totalHigh} ${totalMedium} ${totalLow} ${totalCompleted}
` // ========================================================================= // Section 5: Pflichtenuebersicht // ========================================================================= html += `
5. Pflichtenuebersicht

Uebersicht aller ${obligations.length} Pflichten nach Regulierung und Status:

` for (const [source, obls] of bySource.entries()) { const pending = obls.filter(o => o.status === 'pending').length const inProgress = obls.filter(o => o.status === 'in-progress').length const completed = obls.filter(o => o.status === 'completed').length const overdue = obls.filter(o => o.status === 'overdue').length html += ` ` } html += `
Regulierung Gesamt Ausstehend In Bearbeitung Abgeschlossen Ueberfaellig
${escHtml(source)} ${obls.length} ${pending} ${inProgress} ${completed} ${overdue > 0 ? `${overdue}` : '0'}
` // ========================================================================= // Section 6: Detaillierte Pflichten // ========================================================================= html += `
6. Detaillierte Pflichten
` for (const [source, obls] of bySource.entries()) { // Sort by priority (critical first) then by title const priorityOrder: Record = { critical: 0, high: 1, medium: 2, low: 3 } const sorted = [...obls].sort((a, b) => { const pa = priorityOrder[a.priority] ?? 2 const pb = priorityOrder[b.priority] ?? 2 if (pa !== pb) return pa - pb return a.title.localeCompare(b.title) }) html += `

${escHtml(source)} (${sorted.length} Pflichten)

` for (const o of sorted) { const statusLabel = STATUS_LABELS_DE[o.status] || o.status const statusBadge = STATUS_BADGE_CLASSES[o.status] || 'badge-draft' const priorityLabel = PRIORITY_LABELS_DE[o.priority] || o.priority const priorityBadge = PRIORITY_BADGE_CLASSES[o.priority] || 'badge-draft' const deadlineStr = o.deadline ? formatDateDE(o.deadline) : '—' const evidenceStr = o.evidence && o.evidence.length > 0 ? o.evidence.map(e => escHtml(e)).join(', ') : 'Kein Nachweis' const systemsStr = o.linked_systems && o.linked_systems.length > 0 ? o.linked_systems.map(s => escHtml(s)).join(', ') : '—' html += `
${escHtml(o.title)} ${escHtml(statusLabel)}
${o.linked_vendor_ids && o.linked_vendor_ids.length > 0 ? `` : ''} ${o.notes ? `` : ''}
Rechtsquelle${escHtml(o.source)} ${escHtml(o.source_article || '')}
Beschreibung${escHtml(o.description || '—')}
Prioritaet${escHtml(priorityLabel)}
Status${escHtml(statusLabel)}
Verantwortlich${escHtml(o.responsible || '—')}
Frist${deadlineStr}
Nachweise${evidenceStr}
Betroffene Systeme${systemsStr}
Auftragsverarbeiter${o.linked_vendor_ids.map(id => escHtml(id)).join(', ')}
Notizen${escHtml(o.notes)}
` } } html += `
` // ========================================================================= // Section 7: Verantwortlichkeiten // ========================================================================= html += `
7. Verantwortlichkeiten

Die folgende Rollenmatrix zeigt, welche Personen oder Abteilungen fuer welche Pflichten die Umsetzungsverantwortung tragen:

` for (const [role, obls] of roleMap.entries()) { const openCount = obls.filter(o => o.status !== 'completed').length const titles = obls.slice(0, 5).map(o => escHtml(o.title)) const suffix = obls.length > 5 ? `, ... (+${obls.length - 5})` : '' html += ` ` } html += `
VerantwortlichPflichtenAnzahlDavon offen
${escHtml(role)} ${titles.join('; ')}${suffix} ${obls.length} ${openCount}
` // ========================================================================= // Section 8: Fristen und Termine // ========================================================================= const now = new Date() const withDeadline = obligations .filter(o => o.deadline && o.status !== 'completed') .sort((a, b) => new Date(a.deadline!).getTime() - new Date(b.deadline!).getTime()) const overdue = withDeadline.filter(o => new Date(o.deadline!) < now) const upcoming = withDeadline.filter(o => new Date(o.deadline!) >= now) html += `
8. Fristen und Termine
` if (overdue.length > 0) { html += `

Ueberfaellige Pflichten (${overdue.length})

` for (const o of overdue) { const days = daysBetween(new Date(o.deadline!), now) html += ` ` } html += `
PflichtRegulierungFristTage ueberfaelligPrioritaet
${escHtml(o.title)} ${escHtml(o.source)} ${formatDateDE(o.deadline)} ${days} Tage ${escHtml(PRIORITY_LABELS_DE[o.priority] || o.priority)}
` } if (upcoming.length > 0) { html += `

Anstehende Fristen (${upcoming.length})

` for (const o of upcoming.slice(0, 20)) { const days = daysBetween(now, new Date(o.deadline!)) html += ` ` } if (upcoming.length > 20) { html += ` ` } html += `
PflichtRegulierungFristVerbleibendVerantwortlich
${escHtml(o.title)} ${escHtml(o.source)} ${formatDateDE(o.deadline)} ${days} Tage ${escHtml(o.responsible || '—')}
... und ${upcoming.length - 20} weitere
` } if (withDeadline.length === 0) { html += `

Keine offenen Pflichten mit Fristen vorhanden.

` } html += `
` // ========================================================================= // Section 9: Nachweisverzeichnis // ========================================================================= const withEvidence = obligations.filter(o => o.evidence && o.evidence.length > 0) const withoutEvidence = obligations.filter(o => !o.evidence || o.evidence.length === 0) html += `
9. Nachweisverzeichnis

${withEvidence.length} von ${obligations.length} Pflichten haben Nachweise hinterlegt.

` if (withEvidence.length > 0) { html += ` ` for (const o of withEvidence) { html += ` ` } html += `
PflichtRegulierungNachweiseStatus
${escHtml(o.title)} ${escHtml(o.source)} ${o.evidence!.map(e => escHtml(e)).join(', ')} ${escHtml(STATUS_LABELS_DE[o.status] || o.status)}
` } if (withoutEvidence.length > 0) { html += `

Pflichten ohne Nachweise (${withoutEvidence.length}):

    ` for (const o of withoutEvidence.slice(0, 15)) { html += `
  • ${escHtml(o.title)} (${escHtml(o.source)})
  • ` } if (withoutEvidence.length > 15) { html += `
  • ... und ${withoutEvidence.length - 15} weitere
  • ` } html += `
` } html += `
` // ========================================================================= // Section 10: Compliance-Status // ========================================================================= html += `
10. Compliance-Status
` if (complianceResult) { const scoreClass = complianceResult.score >= 90 ? 'score-excellent' : complianceResult.score >= 75 ? 'score-good' : complianceResult.score >= 50 ? 'score-needs-work' : 'score-poor' const scoreLabel = complianceResult.score >= 90 ? 'Ausgezeichnet' : complianceResult.score >= 75 ? 'Gut' : complianceResult.score >= 50 ? 'Verbesserungswuerdig' : 'Mangelhaft' html += `

${complianceResult.score}/100 ${escHtml(scoreLabel)}

KennzahlWert
Geprueft am${formatDateDE(complianceResult.checkedAt)}
Befunde gesamt${complianceResult.summary.total}
Kritisch${complianceResult.summary.critical}
Hoch${complianceResult.summary.high}
Mittel${complianceResult.summary.medium}
Niedrig${complianceResult.summary.low}
` if (complianceResult.issues.length > 0) { html += `

Befunde nach Schweregrad:

` const severityOrder: ObligationComplianceIssueSeverity[] = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'] for (const sev of severityOrder) { const issuesForSev = complianceResult.issues.filter(i => i.severity === sev) for (const issue of issuesForSev) { html += ` ` } } html += `
SchweregradBefundBetroffene PflichtenEmpfehlung
${OBLIGATION_SEVERITY_LABELS_DE[sev]} ${escHtml(issue.message)} ${issue.affectedObligations.length > 0 ? issue.affectedObligations.length + ' Pflicht(en)' : '—'} ${escHtml(issue.recommendation)}
` } else { html += `

Keine Beanstandungen. Alle Pflichten sind konform.

` } } else { html += `

Compliance-Check wurde noch nicht ausgefuehrt. Fuehren Sie den Check im Pflichtenregister-Tab durch, um den Status in das Dokument aufzunehmen.

` } html += `
` // ========================================================================= // Section 11: Aenderungshistorie // ========================================================================= html += `
11. Aenderungshistorie
` if (revisions.length > 0) { for (const rev of revisions) { html += ` ` } } else { html += ` ` } html += `
VersionDatumAutorAenderungen
${escHtml(rev.version)} ${formatDateDE(rev.date)} ${escHtml(rev.author)} ${escHtml(rev.changes)}
${escHtml(orgHeader.documentVersion)} ${today} ${escHtml(orgHeader.dpoName || orgHeader.responsiblePerson || '—')} Erstversion des Pflichtenregisters
` // ========================================================================= // Footer // ========================================================================= html += ` ` return html } // ============================================================================= // INTERNAL HELPERS // ============================================================================= function escHtml(str: string): string { return str .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') } function formatDateDE(dateStr: string | null | undefined): string { if (!dateStr) return '—' try { const date = new Date(dateStr) if (isNaN(date.getTime())) return '—' return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', }) } catch { return '—' } } function daysBetween(earlier: Date, later: Date): number { const diffMs = later.getTime() - earlier.getTime() return Math.floor(diffMs / (1000 * 60 * 60 * 24)) }