// ============================================================================= // Obligations Document — HTML Builder: Sections 6–11 // ============================================================================= import type { Obligation, ObligationComplianceCheckResult, ObligationComplianceIssueSeverity } from '../obligations-compliance' import { OBLIGATION_SEVERITY_LABELS_DE, OBLIGATION_SEVERITY_COLORS } from '../obligations-compliance' import { STATUS_LABELS_DE, STATUS_BADGE_CLASSES, PRIORITY_LABELS_DE, PRIORITY_BADGE_CLASSES, } from './types-defaults' import { escHtml, formatDateDE, daysBetween } from './helpers' import type { ObligationDocumentOrgHeader, ObligationDocumentRevision } from './types-defaults' export function buildSection6(bySource: Map): string { const priorityOrder: Record = { critical: 0, high: 1, medium: 2, low: 3 } let html = `
6. Detaillierte Pflichten
` for (const [source, obls] of bySource.entries()) { 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 += `
` return html } export function buildSection7(roleMap: Map): string { let 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}
` return html } export function buildSection8(obligations: Obligation[], _today: string): string { 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) let 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 += `
` return html } export function buildSection9(obligations: Obligation[]): string { const withEvidence = obligations.filter(o => o.evidence && o.evidence.length > 0) const withoutEvidence = obligations.filter(o => !o.evidence || o.evidence.length === 0) let 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 += `
` return html } export function buildSection10(complianceResult: ObligationComplianceCheckResult | null): string { let 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 += `
` return html } export function buildSection11( revisions: ObligationDocumentRevision[], orgHeader: ObligationDocumentOrgHeader, today: string ): string { let 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
` return html } export function buildFooter(orgName: string, today: string, version: string): string { return ` ` }