obligations-document, tom-document, loeschfristen-document, compliance-scope-triggers, sdk-flow/flow-data, processing-activities, loeschfristen-baseline-catalog, catalog-registry, dsfa mitigation-library + risk-catalog, vvt-baseline-catalog, vendor contract-review checklists + findings, demo-data, tom-compliance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
242 lines
8.1 KiB
TypeScript
242 lines
8.1 KiB
TypeScript
// =============================================================================
|
||
// TOM Document — HTML Sections 7–11
|
||
// =============================================================================
|
||
|
||
import type { DerivedTOM, ControlCategory } from '../tom-generator/types'
|
||
import { SDM_CATEGORY_MAPPING } from '../tom-generator/types'
|
||
import type { TOMComplianceCheckResult, TOMComplianceIssueSeverity } from '../tom-compliance'
|
||
import type { TOMDocumentOrgHeader, TOMDocumentRevision } from './types-defaults'
|
||
import { SEVERITY_LABELS_DE, SEVERITY_COLORS } from './types-defaults'
|
||
import { escHtml, formatDateDE } from './helpers'
|
||
|
||
export function buildSections7to11(
|
||
orgName: string,
|
||
orgHeader: TOMDocumentOrgHeader,
|
||
tomsByCategory: Map<ControlCategory, DerivedTOM[]>,
|
||
roleMap: Map<string, string[]>,
|
||
complianceResult: TOMComplianceCheckResult | null,
|
||
revisions: TOMDocumentRevision[],
|
||
today: string
|
||
): string {
|
||
let html = ''
|
||
html += buildSection7(tomsByCategory)
|
||
html += buildSection8(roleMap)
|
||
html += buildSection9(orgHeader)
|
||
html += buildSection10(complianceResult)
|
||
html += buildSection11(revisions, orgHeader, today)
|
||
return html
|
||
}
|
||
|
||
function buildSection7(tomsByCategory: Map<ControlCategory, DerivedTOM[]>): string {
|
||
const allSDMGoals = [
|
||
'Verfuegbarkeit',
|
||
'Integritaet',
|
||
'Vertraulichkeit',
|
||
'Nichtverkettung',
|
||
'Intervenierbarkeit',
|
||
'Transparenz',
|
||
'Datenminimierung',
|
||
] as const
|
||
|
||
const sdmGoals: Array<{ goal: string; categories: ControlCategory[] }> = []
|
||
for (const goal of allSDMGoals) {
|
||
const cats: ControlCategory[] = []
|
||
for (const [cat, goals] of Object.entries(SDM_CATEGORY_MAPPING)) {
|
||
if (goals.includes(goal)) {
|
||
cats.push(cat as ControlCategory)
|
||
}
|
||
}
|
||
sdmGoals.push({ goal, categories: cats })
|
||
}
|
||
|
||
let html = `
|
||
<div class="section page-break">
|
||
<div class="section-header">7. SDM Gewaehrleistungsziele</div>
|
||
<div class="section-body">
|
||
<p>Die folgende Tabelle zeigt die Abdeckung der sieben Gewaehrleistungsziele des
|
||
Standard-Datenschutzmodells (SDM) durch die implementierten Massnahmen:</p>
|
||
<table>
|
||
<tr>
|
||
<th>Gewaehrleistungsziel</th>
|
||
<th>Abgedeckt</th>
|
||
<th>Gesamt</th>
|
||
<th>Abdeckung (%)</th>
|
||
</tr>
|
||
`
|
||
|
||
for (const { goal, categories } of sdmGoals) {
|
||
let totalInGoal = 0
|
||
let implementedInGoal = 0
|
||
for (const cat of categories) {
|
||
const tomsInCat = tomsByCategory.get(cat) || []
|
||
totalInGoal += tomsInCat.length
|
||
implementedInGoal += tomsInCat.filter(t => t.implementationStatus === 'IMPLEMENTED').length
|
||
}
|
||
const percentage = totalInGoal > 0 ? Math.round((implementedInGoal / totalInGoal) * 100) : 0
|
||
|
||
html += ` <tr>
|
||
<td>${escHtml(goal)}</td>
|
||
<td>${implementedInGoal}</td>
|
||
<td>${totalInGoal}</td>
|
||
<td>${percentage}%</td>
|
||
</tr>
|
||
`
|
||
}
|
||
|
||
html += ` </table>
|
||
</div>
|
||
</div>
|
||
`
|
||
return html
|
||
}
|
||
|
||
function buildSection8(roleMap: Map<string, string[]>): string {
|
||
let html = `
|
||
<div class="section">
|
||
<div class="section-header">8. Verantwortlichkeiten</div>
|
||
<div class="section-body">
|
||
<p>Die folgende Rollenmatrix zeigt, welche Personen oder Abteilungen fuer welche Massnahmen
|
||
die Umsetzungsverantwortung tragen:</p>
|
||
<table>
|
||
<tr><th>Rolle / Verantwortlich</th><th>Massnahmen</th><th>Anzahl</th></tr>
|
||
`
|
||
for (const [role, controls] of roleMap.entries()) {
|
||
html += ` <tr>
|
||
<td>${escHtml(role)}</td>
|
||
<td>${controls.map(c => escHtml(c)).join(', ')}</td>
|
||
<td>${controls.length}</td>
|
||
</tr>
|
||
`
|
||
}
|
||
|
||
html += ` </table>
|
||
</div>
|
||
</div>
|
||
`
|
||
return html
|
||
}
|
||
|
||
function buildSection9(orgHeader: TOMDocumentOrgHeader): string {
|
||
return `
|
||
<div class="section">
|
||
<div class="section-header">9. Pruef- und Revisionszyklus</div>
|
||
<div class="section-body">
|
||
<table>
|
||
<tr><th>Eigenschaft</th><th>Wert</th></tr>
|
||
<tr><td>Aktuelles Pruefintervall</td><td>${escHtml(orgHeader.reviewInterval)}</td></tr>
|
||
<tr><td>Letzte Pruefung</td><td>${formatDateDE(orgHeader.lastReviewDate)}</td></tr>
|
||
<tr><td>Naechste Pruefung</td><td>${formatDateDE(orgHeader.nextReviewDate)}</td></tr>
|
||
<tr><td>Aktuelle Version</td><td>${escHtml(orgHeader.documentVersion)}</td></tr>
|
||
</table>
|
||
<p style="margin-top: 8px;">Bei jeder Pruefung wird die TOM-Dokumentation auf folgende Punkte ueberprueft:</p>
|
||
<ul style="margin: 8px 0 8px 24px;">
|
||
<li>Vollstaendigkeit aller Massnahmen (neue Systeme oder Verarbeitungen erfasst?)</li>
|
||
<li>Aktualitaet des Umsetzungsstatus (Aenderungen seit letzter Pruefung?)</li>
|
||
<li>Wirksamkeit der technischen Massnahmen (Penetration-Tests, Audit-Ergebnisse)</li>
|
||
<li>Angemessenheit der organisatorischen Massnahmen (Schulungen, Richtlinien aktuell?)</li>
|
||
<li>Abdeckung aller SDM-Gewaehrleistungsziele</li>
|
||
<li>Zuordnung von Verantwortlichkeiten zu allen Massnahmen</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
`
|
||
}
|
||
|
||
function buildSection10(complianceResult: TOMComplianceCheckResult | null): string {
|
||
let html = `
|
||
<div class="section page-break">
|
||
<div class="section-header">10. Compliance-Status</div>
|
||
<div class="section-body">
|
||
`
|
||
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 += ` <p><span class="score-box ${scoreClass}">${complianceResult.score}/100</span> ${escHtml(scoreLabel)}</p>
|
||
<table style="margin-top: 12px;">
|
||
<tr><th>Kennzahl</th><th>Wert</th></tr>
|
||
<tr><td>Gepruefte Massnahmen</td><td>${complianceResult.stats.total}</td></tr>
|
||
<tr><td>Bestanden</td><td>${complianceResult.stats.passed}</td></tr>
|
||
<tr><td>Beanstandungen</td><td>${complianceResult.stats.failed}</td></tr>
|
||
</table>
|
||
`
|
||
if (complianceResult.issues.length > 0) {
|
||
html += ` <p style="margin-top: 12px;"><strong>Befunde nach Schweregrad:</strong></p>
|
||
<table>
|
||
<tr><th>Schweregrad</th><th>Anzahl</th><th>Befunde</th></tr>
|
||
`
|
||
const severityOrder: TOMComplianceIssueSeverity[] = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']
|
||
for (const sev of severityOrder) {
|
||
const count = complianceResult.stats.bySeverity[sev]
|
||
if (count === 0) continue
|
||
const issuesForSev = complianceResult.issues.filter(i => i.severity === sev)
|
||
html += ` <tr>
|
||
<td><span class="badge badge-${sev.toLowerCase()}" style="color: ${SEVERITY_COLORS[sev]}">${SEVERITY_LABELS_DE[sev]}</span></td>
|
||
<td>${count}</td>
|
||
<td>${issuesForSev.map(i => escHtml(i.title)).join('; ')}</td>
|
||
</tr>
|
||
`
|
||
}
|
||
html += ` </table>
|
||
`
|
||
} else {
|
||
html += ` <p style="margin-top: 8px;"><em>Keine Beanstandungen. Alle Massnahmen sind konform.</em></p>
|
||
`
|
||
}
|
||
} else {
|
||
html += ` <p><em>Compliance-Check wurde noch nicht ausgefuehrt. Fuehren Sie den Check im
|
||
Export-Tab durch, um den Status in das Dokument aufzunehmen.</em></p>
|
||
`
|
||
}
|
||
|
||
html += ` </div>
|
||
</div>
|
||
`
|
||
return html
|
||
}
|
||
|
||
function buildSection11(
|
||
revisions: TOMDocumentRevision[],
|
||
orgHeader: TOMDocumentOrgHeader,
|
||
today: string
|
||
): string {
|
||
let html = `
|
||
<div class="section">
|
||
<div class="section-header">11. Aenderungshistorie</div>
|
||
<div class="section-body">
|
||
<table>
|
||
<tr><th>Version</th><th>Datum</th><th>Autor</th><th>Aenderungen</th></tr>
|
||
`
|
||
if (revisions.length > 0) {
|
||
for (const rev of revisions) {
|
||
html += ` <tr>
|
||
<td>${escHtml(rev.version)}</td>
|
||
<td>${formatDateDE(rev.date)}</td>
|
||
<td>${escHtml(rev.author)}</td>
|
||
<td>${escHtml(rev.changes)}</td>
|
||
</tr>
|
||
`
|
||
}
|
||
} else {
|
||
html += ` <tr>
|
||
<td>${escHtml(orgHeader.documentVersion)}</td>
|
||
<td>${today}</td>
|
||
<td>${escHtml(orgHeader.dpoName || orgHeader.responsiblePerson || '-')}</td>
|
||
<td>Erstversion der TOM-Dokumentation</td>
|
||
</tr>
|
||
`
|
||
}
|
||
|
||
html += ` </table>
|
||
</div>
|
||
</div>
|
||
`
|
||
return html
|
||
}
|