obligations-document/html-builder.ts (620→304 LOC): extract sections 6-11 and footer into html-builder-sections-6-11.ts (339 LOC). loeschfristen-document/html-builder.ts (603→353 LOC): extract sections 6-12 into html-builder-sections-6-12.ts (259 LOC). Both orchestrators re-export from siblings; zero behavior change. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
305 lines
11 KiB
TypeScript
305 lines
11 KiB
TypeScript
// =============================================================================
|
|
// Obligations Document — HTML Document Builder
|
|
// =============================================================================
|
|
|
|
import type { Obligation, ObligationComplianceCheckResult } from '../obligations-compliance'
|
|
import type { ObligationDocumentOrgHeader, ObligationDocumentRevision } from './types-defaults'
|
|
import {
|
|
STATUS_LABELS_DE,
|
|
STATUS_BADGE_CLASSES,
|
|
PRIORITY_LABELS_DE,
|
|
PRIORITY_BADGE_CLASSES,
|
|
} from './types-defaults'
|
|
import { escHtml, formatDateDE } from './helpers'
|
|
import { getDocumentStyles } from './html-styles'
|
|
import {
|
|
buildSection6,
|
|
buildSection7,
|
|
buildSection8,
|
|
buildSection9,
|
|
buildSection10,
|
|
buildSection11,
|
|
buildFooter,
|
|
} from './html-builder-sections-6-11'
|
|
|
|
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<string, Obligation[]>()
|
|
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<string, Obligation[]>()
|
|
for (const o of obligations) {
|
|
const role = o.responsible || 'Nicht zugewiesen'
|
|
if (!roleMap.has(role)) roleMap.set(role, [])
|
|
roleMap.get(role)!.push(o)
|
|
}
|
|
|
|
const distinctSources = Array.from(bySource.keys()).sort()
|
|
|
|
let html = getDocumentStyles(`Pflichtenregister — ${escHtml(orgName)}`)
|
|
|
|
html += buildCoverPage(orgHeader, orgName, today)
|
|
html += buildTOC()
|
|
html += buildSection1(orgName)
|
|
html += buildSection2(orgName, orgHeader, bySource, obligations, distinctSources)
|
|
html += buildSection3()
|
|
html += buildSection4(bySource, obligations)
|
|
html += buildSection5(bySource)
|
|
html += buildSection6(bySource)
|
|
html += buildSection7(roleMap)
|
|
html += buildSection8(obligations, today)
|
|
html += buildSection9(obligations)
|
|
html += buildSection10(complianceResult)
|
|
html += buildSection11(revisions, orgHeader, today)
|
|
html += buildFooter(orgName, today, orgHeader.documentVersion)
|
|
|
|
return html
|
|
}
|
|
|
|
function buildCoverPage(
|
|
orgHeader: ObligationDocumentOrgHeader,
|
|
orgName: string,
|
|
today: string
|
|
): string {
|
|
return `
|
|
<div class="cover">
|
|
<h1>Pflichtenregister</h1>
|
|
<div class="subtitle">Regulatorische Pflichten — DSGVO, AI Act, NIS2 und weitere</div>
|
|
<div class="org-info">
|
|
<div><span class="label">Organisation:</span> ${escHtml(orgName)}</div>
|
|
${orgHeader.industry ? `<div><span class="label">Branche:</span> ${escHtml(orgHeader.industry)}</div>` : ''}
|
|
${orgHeader.dpoName ? `<div><span class="label">DSB:</span> ${escHtml(orgHeader.dpoName)}</div>` : ''}
|
|
${orgHeader.dpoContact ? `<div><span class="label">DSB-Kontakt:</span> ${escHtml(orgHeader.dpoContact)}</div>` : ''}
|
|
${orgHeader.responsiblePerson ? `<div><span class="label">Verantwortlicher:</span> ${escHtml(orgHeader.responsiblePerson)}</div>` : ''}
|
|
${orgHeader.legalDepartment ? `<div><span class="label">Rechtsabteilung:</span> ${escHtml(orgHeader.legalDepartment)}</div>` : ''}
|
|
</div>
|
|
<div class="legal-ref">
|
|
Version ${escHtml(orgHeader.documentVersion)} | Stand: ${today}<br/>
|
|
Letzte Pruefung: ${formatDateDE(orgHeader.lastReviewDate)} | Naechste Pruefung: ${formatDateDE(orgHeader.nextReviewDate)}<br/>
|
|
Pruefintervall: ${escHtml(orgHeader.reviewInterval)}
|
|
</div>
|
|
</div>
|
|
`
|
|
}
|
|
|
|
function buildTOC(): string {
|
|
const sections = [
|
|
'Ziel und Zweck',
|
|
'Geltungsbereich',
|
|
'Methodik',
|
|
'Regulatorische Grundlagen',
|
|
'Pflichtenuebersicht',
|
|
'Detaillierte Pflichten',
|
|
'Verantwortlichkeiten',
|
|
'Fristen und Termine',
|
|
'Nachweisverzeichnis',
|
|
'Compliance-Status',
|
|
'Aenderungshistorie',
|
|
]
|
|
|
|
return `
|
|
<div class="toc">
|
|
<h2>Inhaltsverzeichnis</h2>
|
|
${sections.map((s, i) => `<div class="toc-entry"><span><span class="toc-num">${i + 1}.</span> ${escHtml(s)}</span></div>`).join('\n ')}
|
|
</div>
|
|
`
|
|
}
|
|
|
|
function buildSection1(orgName: string): string {
|
|
return `
|
|
<div class="section">
|
|
<div class="section-header">1. Ziel und Zweck</div>
|
|
<div class="section-body">
|
|
<p>Dieses Pflichtenregister dokumentiert alle regulatorischen Pflichten, denen
|
|
<strong>${escHtml(orgName)}</strong> unterliegt. Es dient der systematischen Erfassung,
|
|
Ueberwachung und Nachverfolgung aller Compliance-Anforderungen aus den anwendbaren
|
|
Regulierungen.</p>
|
|
<p style="margin-top: 8px;">Das Register erfuellt folgende Zwecke:</p>
|
|
<ul style="margin: 8px 0 8px 24px;">
|
|
<li>Vollstaendige Erfassung aller anwendbaren regulatorischen Pflichten</li>
|
|
<li>Zuordnung von Verantwortlichkeiten und Fristen</li>
|
|
<li>Nachverfolgung des Umsetzungsstatus</li>
|
|
<li>Dokumentation von Nachweisen fuer Audits</li>
|
|
<li>Identifikation von Compliance-Luecken und Handlungsbedarf</li>
|
|
</ul>
|
|
<table>
|
|
<tr><th>Rechtsrahmen</th><th>Relevanz</th></tr>
|
|
<tr><td><strong>DSGVO (EU) 2016/679</strong></td><td>Datenschutz-Grundverordnung — Kernregulierung fuer personenbezogene Daten</td></tr>
|
|
<tr><td><strong>AI Act (EU) 2024/1689</strong></td><td>KI-Verordnung — Anforderungen an KI-Systeme nach Risikoklasse</td></tr>
|
|
<tr><td><strong>NIS2 (EU) 2022/2555</strong></td><td>Netzwerk- und Informationssicherheit — Cybersicherheitspflichten</td></tr>
|
|
<tr><td><strong>BDSG</strong></td><td>Bundesdatenschutzgesetz — Nationale Ergaenzung zur DSGVO</td></tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
`
|
|
}
|
|
|
|
function buildSection2(
|
|
orgName: string,
|
|
orgHeader: ObligationDocumentOrgHeader,
|
|
bySource: Map<string, Obligation[]>,
|
|
obligations: Obligation[],
|
|
distinctSources: string[]
|
|
): string {
|
|
let html = `
|
|
<div class="section">
|
|
<div class="section-header">2. Geltungsbereich</div>
|
|
<div class="section-body">
|
|
<p>Dieses Pflichtenregister gilt fuer alle Geschaeftsprozesse und IT-Systeme von
|
|
<strong>${escHtml(orgName)}</strong>${orgHeader.industry ? ` (Branche: ${escHtml(orgHeader.industry)})` : ''}.</p>
|
|
<p style="margin-top: 8px;">Anwendbare Regulierungen:</p>
|
|
<table>
|
|
<tr><th>Regulierung</th><th>Anzahl Pflichten</th><th>Status</th></tr>
|
|
`
|
|
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 += ` <tr>
|
|
<td>${escHtml(source)}</td>
|
|
<td>${obls.length}</td>
|
|
<td>${completed}/${obls.length} abgeschlossen (${pct}%)</td>
|
|
</tr>
|
|
`
|
|
}
|
|
html += ` </table>
|
|
<p>Insgesamt umfasst dieses Register <strong>${obligations.length}</strong> Pflichten aus
|
|
<strong>${distinctSources.length}</strong> Regulierungen.</p>
|
|
</div>
|
|
</div>
|
|
`
|
|
return html
|
|
}
|
|
|
|
function buildSection3(): string {
|
|
return `
|
|
<div class="section">
|
|
<div class="section-header">3. Methodik</div>
|
|
<div class="section-body">
|
|
<p>Die Identifikation und Bewertung der Pflichten erfolgt in drei Schritten:</p>
|
|
<div class="principle"><strong>Pflicht-Identifikation:</strong> Systematische Analyse aller anwendbaren Regulierungen und Extraktion der einzelnen Pflichten mit Artikel-Referenz, Beschreibung und Zielgruppe.</div>
|
|
<div class="principle"><strong>Bewertung und Priorisierung:</strong> Jede Pflicht wird nach Prioritaet (kritisch, hoch, mittel, niedrig) und Dringlichkeit (Frist) bewertet. Die Bewertung basiert auf dem Risikopotenzial bei Nichterfuellung.</div>
|
|
<div class="principle"><strong>Ueberwachung und Nachverfolgung:</strong> Regelmaessige Pruefung des Umsetzungsstatus, Aktualisierung der Fristen und Dokumentation von Nachweisen.</div>
|
|
<p style="margin-top: 12px;">Die Pflichten werden ueber einen automatisierten Compliance-Check geprueft, der
|
|
11 Kriterien umfasst (siehe Abschnitt 10: Compliance-Status).</p>
|
|
</div>
|
|
</div>
|
|
`
|
|
}
|
|
|
|
function buildSection4(bySource: Map<string, Obligation[]>, obligations: Obligation[]): string {
|
|
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
|
|
|
|
let html = `
|
|
<div class="section page-break">
|
|
<div class="section-header">4. Regulatorische Grundlagen</div>
|
|
<div class="section-body">
|
|
<p>Die folgende Tabelle zeigt die regulatorischen Grundlagen mit Artikelzahl und Umsetzungsstatus:</p>
|
|
<table>
|
|
<tr>
|
|
<th>Regulierung</th>
|
|
<th>Pflichten</th>
|
|
<th>Kritisch</th>
|
|
<th>Hoch</th>
|
|
<th>Mittel</th>
|
|
<th>Niedrig</th>
|
|
<th>Abgeschlossen</th>
|
|
</tr>
|
|
`
|
|
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 += ` <tr>
|
|
<td><strong>${escHtml(source)}</strong></td>
|
|
<td>${obls.length}</td>
|
|
<td>${critical}</td>
|
|
<td>${high}</td>
|
|
<td>${medium}</td>
|
|
<td>${low}</td>
|
|
<td>${completed}</td>
|
|
</tr>
|
|
`
|
|
}
|
|
|
|
html += ` <tr style="font-weight: 700; background: #f5f3ff;">
|
|
<td>Gesamt</td>
|
|
<td>${obligations.length}</td>
|
|
<td>${totalCritical}</td>
|
|
<td>${totalHigh}</td>
|
|
<td>${totalMedium}</td>
|
|
<td>${totalLow}</td>
|
|
<td>${totalCompleted}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
`
|
|
return html
|
|
}
|
|
|
|
function buildSection5(bySource: Map<string, Obligation[]>): string {
|
|
let html = `
|
|
<div class="section">
|
|
<div class="section-header">5. Pflichtenuebersicht</div>
|
|
<div class="section-body">
|
|
<p>Uebersicht aller Pflichten nach Regulierung und Status:</p>
|
|
<table>
|
|
<tr>
|
|
<th>Regulierung</th>
|
|
<th>Gesamt</th>
|
|
<th>Ausstehend</th>
|
|
<th>In Bearbeitung</th>
|
|
<th>Abgeschlossen</th>
|
|
<th>Ueberfaellig</th>
|
|
</tr>
|
|
`
|
|
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 += ` <tr>
|
|
<td>${escHtml(source)}</td>
|
|
<td>${obls.length}</td>
|
|
<td>${pending}</td>
|
|
<td>${inProgress}</td>
|
|
<td>${completed}</td>
|
|
<td>${overdue > 0 ? `<span class="badge badge-critical">${overdue}</span>` : '0'}</td>
|
|
</tr>
|
|
`
|
|
}
|
|
|
|
html += ` </table>
|
|
</div>
|
|
</div>
|
|
`
|
|
return html
|
|
}
|
|
|