Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
539 lines
20 KiB
TypeScript
539 lines
20 KiB
TypeScript
// =============================================================================
|
|
// Loeschfristen Module - Profiling Wizard
|
|
// 4-Step Profiling (15 Fragen) zur Generierung von Baseline-Loeschrichtlinien
|
|
// =============================================================================
|
|
|
|
import type { LoeschfristPolicy, StorageLocation } from './loeschfristen-types'
|
|
import { BASELINE_TEMPLATES, type BaselineTemplate, templateToPolicy } from './loeschfristen-baseline-catalog'
|
|
|
|
// =============================================================================
|
|
// TYPES
|
|
// =============================================================================
|
|
|
|
export type ProfilingStepId = 'organization' | 'data-categories' | 'systems' | 'special'
|
|
|
|
export interface ProfilingQuestion {
|
|
id: string
|
|
step: ProfilingStepId
|
|
question: string // German
|
|
helpText?: string
|
|
type: 'single' | 'multi' | 'boolean' | 'number'
|
|
options?: { value: string; label: string }[]
|
|
required: boolean
|
|
}
|
|
|
|
export interface ProfilingAnswer {
|
|
questionId: string
|
|
value: string | string[] | boolean | number
|
|
}
|
|
|
|
export interface ProfilingStep {
|
|
id: ProfilingStepId
|
|
title: string
|
|
description: string
|
|
questions: ProfilingQuestion[]
|
|
}
|
|
|
|
export interface ProfilingResult {
|
|
matchedTemplates: BaselineTemplate[]
|
|
generatedPolicies: LoeschfristPolicy[]
|
|
additionalStorageLocations: StorageLocation[]
|
|
hasLegalHoldRequirement: boolean
|
|
}
|
|
|
|
// =============================================================================
|
|
// PROFILING STEPS (4 Steps, 15 Questions)
|
|
// =============================================================================
|
|
|
|
export const PROFILING_STEPS: ProfilingStep[] = [
|
|
// =========================================================================
|
|
// Step 1: Organisation (4 Fragen)
|
|
// =========================================================================
|
|
{
|
|
id: 'organization',
|
|
title: 'Organisation',
|
|
description: 'Allgemeine Informationen zu Ihrem Unternehmen, um branchenspezifische Loeschfristen zu ermitteln.',
|
|
questions: [
|
|
{
|
|
id: 'org-branche',
|
|
step: 'organization',
|
|
question: 'In welcher Branche ist Ihr Unternehmen taetig?',
|
|
helpText: 'Die Branche bestimmt, welche branchenspezifischen Aufbewahrungspflichten relevant sind.',
|
|
type: 'single',
|
|
options: [
|
|
{ value: 'it-software', label: 'IT / Software' },
|
|
{ value: 'handel', label: 'Handel' },
|
|
{ value: 'dienstleistung', label: 'Dienstleistung' },
|
|
{ value: 'gesundheitswesen', label: 'Gesundheitswesen' },
|
|
{ value: 'bildung', label: 'Bildung' },
|
|
{ value: 'fertigung-industrie', label: 'Fertigung / Industrie' },
|
|
{ value: 'finanzwesen', label: 'Finanzwesen' },
|
|
{ value: 'oeffentlicher-sektor', label: 'Oeffentlicher Sektor' },
|
|
{ value: 'sonstige', label: 'Sonstige' },
|
|
],
|
|
required: true,
|
|
},
|
|
{
|
|
id: 'org-mitarbeiter',
|
|
step: 'organization',
|
|
question: 'Wie viele Mitarbeiter hat Ihr Unternehmen?',
|
|
helpText: 'Die Unternehmensgroesse beeinflusst den Umfang der erforderlichen Loeschkonzepte.',
|
|
type: 'single',
|
|
options: [
|
|
{ value: '<10', label: 'Weniger als 10' },
|
|
{ value: '10-49', label: '10 bis 49' },
|
|
{ value: '50-249', label: '50 bis 249' },
|
|
{ value: '250+', label: '250 und mehr' },
|
|
],
|
|
required: true,
|
|
},
|
|
{
|
|
id: 'org-geschaeftsmodell',
|
|
step: 'organization',
|
|
question: 'Welches Geschaeftsmodell verfolgen Sie?',
|
|
helpText: 'B2B und B2C haben unterschiedliche Anforderungen an die Datenhaltung.',
|
|
type: 'single',
|
|
options: [
|
|
{ value: 'b2b', label: 'B2B (Geschaeftskunden)' },
|
|
{ value: 'b2c', label: 'B2C (Endkunden)' },
|
|
{ value: 'beides', label: 'Beides (B2B und B2C)' },
|
|
],
|
|
required: true,
|
|
},
|
|
{
|
|
id: 'org-website',
|
|
step: 'organization',
|
|
question: 'Betreiben Sie eine Website oder Online-Praesenz?',
|
|
helpText: 'Websites erzeugen Webserver-Logs und erfordern Cookie-Consent-Verwaltung.',
|
|
type: 'boolean',
|
|
required: true,
|
|
},
|
|
],
|
|
},
|
|
|
|
// =========================================================================
|
|
// Step 2: Datenkategorien (5 Fragen)
|
|
// =========================================================================
|
|
{
|
|
id: 'data-categories',
|
|
title: 'Datenkategorien',
|
|
description: 'Welche Arten personenbezogener Daten verarbeiten Sie? Dies bestimmt die relevanten Aufbewahrungsfristen.',
|
|
questions: [
|
|
{
|
|
id: 'data-hr',
|
|
step: 'data-categories',
|
|
question: 'Verarbeiten Sie HR-/Personaldaten (Personalakten, Gehaltsabrechnungen, Zeiterfassung)?',
|
|
helpText: 'Personalakten unterliegen umfangreichen gesetzlichen Aufbewahrungspflichten (bis zu 10 Jahre).',
|
|
type: 'boolean',
|
|
required: true,
|
|
},
|
|
{
|
|
id: 'data-buchhaltung',
|
|
step: 'data-categories',
|
|
question: 'Fuehren Sie eine Buchhaltung mit Finanzdaten (Rechnungen, Belege, Steuererklarungen)?',
|
|
helpText: 'Buchhaltungsunterlagen muessen gemaess HGB und AO bis zu 10 Jahre aufbewahrt werden.',
|
|
type: 'boolean',
|
|
required: true,
|
|
},
|
|
{
|
|
id: 'data-vertraege',
|
|
step: 'data-categories',
|
|
question: 'Verwalten Sie Vertraege mit Kunden oder Lieferanten?',
|
|
helpText: 'Vertragsunterlagen und Geschaeftsbriefe haben spezifische Aufbewahrungspflichten.',
|
|
type: 'boolean',
|
|
required: true,
|
|
},
|
|
{
|
|
id: 'data-marketing',
|
|
step: 'data-categories',
|
|
question: 'Betreiben Sie Marketing-Aktivitaeten (Newsletter, CRM-Kampagnen)?',
|
|
helpText: 'Marketing-Einwilligungen und Kontakthistorien muessen dokumentiert und verwaltet werden.',
|
|
type: 'boolean',
|
|
required: true,
|
|
},
|
|
{
|
|
id: 'data-video',
|
|
step: 'data-categories',
|
|
question: 'Setzen Sie Videoueberwachung ein?',
|
|
helpText: 'Videoueberwachungsdaten haben besonders kurze Loeschfristen (in der Regel 72 Stunden).',
|
|
type: 'boolean',
|
|
required: true,
|
|
},
|
|
],
|
|
},
|
|
|
|
// =========================================================================
|
|
// Step 3: Systeme (3 Fragen)
|
|
// =========================================================================
|
|
{
|
|
id: 'systems',
|
|
title: 'Systeme & Infrastruktur',
|
|
description: 'Welche IT-Systeme und Infrastruktur nutzen Sie? Dies beeinflusst die Speicherorte in Ihrem Loeschkonzept.',
|
|
questions: [
|
|
{
|
|
id: 'sys-cloud',
|
|
step: 'systems',
|
|
question: 'Nutzen Sie Cloud-Dienste zur Datenspeicherung oder -verarbeitung?',
|
|
helpText: 'Cloud-Speicherorte muessen in den Loeschrichtlinien als separate Speicherorte dokumentiert werden.',
|
|
type: 'boolean',
|
|
required: true,
|
|
},
|
|
{
|
|
id: 'sys-backup',
|
|
step: 'systems',
|
|
question: 'Haben Sie Backup-Systeme im Einsatz?',
|
|
helpText: 'Backups erfordern eine eigene Loeschstrategie, da Daten dort nach der primaeren Loeschung weiter existieren koennen.',
|
|
type: 'boolean',
|
|
required: true,
|
|
},
|
|
{
|
|
id: 'sys-erp',
|
|
step: 'systems',
|
|
question: 'Setzen Sie ein ERP- oder CRM-System ein?',
|
|
helpText: 'ERP-/CRM-Systeme sind haeufig zentrale Speicherorte fuer Kunden- und Geschaeftsdaten.',
|
|
type: 'boolean',
|
|
required: true,
|
|
},
|
|
],
|
|
},
|
|
|
|
// =========================================================================
|
|
// Step 4: Spezielle Anforderungen (3 Fragen)
|
|
// =========================================================================
|
|
{
|
|
id: 'special',
|
|
title: 'Spezielle Anforderungen',
|
|
description: 'Gibt es besondere rechtliche oder organisatorische Anforderungen, die Ihr Loeschkonzept beeinflussen?',
|
|
questions: [
|
|
{
|
|
id: 'special-legal-hold',
|
|
step: 'special',
|
|
question: 'Gibt es Legal-Hold-Anforderungen (z.B. laufende Rechtsstreitigkeiten, behoerdliche Untersuchungen)?',
|
|
helpText: 'Bei einem Legal Hold muessen betroffene Daten trotz abgelaufener Loeschfristen aufbewahrt werden.',
|
|
type: 'boolean',
|
|
required: true,
|
|
},
|
|
{
|
|
id: 'special-archivierung',
|
|
step: 'special',
|
|
question: 'Benoetigen Sie eine Langzeitarchivierung von Dokumenten?',
|
|
helpText: 'Langzeitarchivierung kann ueber die gesetzlichen Mindestfristen hinausgehen und erfordert eine gesonderte Rechtfertigung.',
|
|
type: 'boolean',
|
|
required: true,
|
|
},
|
|
{
|
|
id: 'special-gesundheit',
|
|
step: 'special',
|
|
question: 'Verarbeiten Sie Gesundheitsdaten (z.B. Krankmeldungen, Arbeitsmedizin)?',
|
|
helpText: 'Gesundheitsdaten sind besonders schuetzenswerte Daten nach Art. 9 DSGVO und unterliegen strengeren Anforderungen.',
|
|
type: 'boolean',
|
|
required: true,
|
|
},
|
|
],
|
|
},
|
|
]
|
|
|
|
// =============================================================================
|
|
// HELPER FUNCTIONS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Retrieve the value of a specific answer by question ID.
|
|
*/
|
|
export function getAnswerValue(answers: ProfilingAnswer[], questionId: string): unknown {
|
|
const answer = answers.find(a => a.questionId === questionId)
|
|
return answer?.value ?? undefined
|
|
}
|
|
|
|
/**
|
|
* Check whether all required questions in a given step have been answered.
|
|
*/
|
|
export function isStepComplete(answers: ProfilingAnswer[], stepId: ProfilingStepId): boolean {
|
|
const step = PROFILING_STEPS.find(s => s.id === stepId)
|
|
if (!step) return false
|
|
|
|
return step.questions
|
|
.filter(q => q.required)
|
|
.every(q => {
|
|
const answer = answers.find(a => a.questionId === q.id)
|
|
if (!answer) return false
|
|
|
|
// Check that the value is not empty
|
|
const val = answer.value
|
|
if (val === undefined || val === null) return false
|
|
if (typeof val === 'string' && val.trim() === '') return false
|
|
if (Array.isArray(val) && val.length === 0) return false
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Calculate overall profiling progress as a percentage (0-100).
|
|
*/
|
|
export function getProfilingProgress(answers: ProfilingAnswer[]): number {
|
|
const totalRequired = PROFILING_STEPS.reduce(
|
|
(sum, step) => sum + step.questions.filter(q => q.required).length,
|
|
0
|
|
)
|
|
if (totalRequired === 0) return 100
|
|
|
|
const answeredRequired = PROFILING_STEPS.reduce((sum, step) => {
|
|
return (
|
|
sum +
|
|
step.questions.filter(q => q.required).filter(q => {
|
|
const answer = answers.find(a => a.questionId === q.id)
|
|
if (!answer) return false
|
|
const val = answer.value
|
|
if (val === undefined || val === null) return false
|
|
if (typeof val === 'string' && val.trim() === '') return false
|
|
if (Array.isArray(val) && val.length === 0) return false
|
|
return true
|
|
}).length
|
|
)
|
|
}, 0)
|
|
|
|
return Math.round((answeredRequired / totalRequired) * 100)
|
|
}
|
|
|
|
// =============================================================================
|
|
// CORE GENERATOR
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Generate deletion policies based on the profiling answers.
|
|
*
|
|
* Logic:
|
|
* - Match baseline templates based on boolean and categorical answers
|
|
* - Deduplicate matched templates by templateId
|
|
* - Convert matched templates to full LoeschfristPolicy objects
|
|
* - Add additional storage locations (Cloud, Backup) if applicable
|
|
* - Detect legal hold requirements
|
|
*/
|
|
export function generatePoliciesFromProfile(answers: ProfilingAnswer[]): ProfilingResult {
|
|
const matchedTemplateIds = new Set<string>()
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Helper to get a boolean answer
|
|
// -------------------------------------------------------------------------
|
|
const getBool = (questionId: string): boolean => {
|
|
const val = getAnswerValue(answers, questionId)
|
|
return val === true
|
|
}
|
|
|
|
const getString = (questionId: string): string => {
|
|
const val = getAnswerValue(answers, questionId)
|
|
return typeof val === 'string' ? val : ''
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Always-included templates (universally recommended)
|
|
// -------------------------------------------------------------------------
|
|
matchedTemplateIds.add('protokolle-gesellschafter')
|
|
|
|
// -------------------------------------------------------------------------
|
|
// HR data (data-hr = true)
|
|
// -------------------------------------------------------------------------
|
|
if (getBool('data-hr')) {
|
|
matchedTemplateIds.add('personal-akten')
|
|
matchedTemplateIds.add('gehaltsabrechnungen')
|
|
matchedTemplateIds.add('zeiterfassung')
|
|
matchedTemplateIds.add('bewerbungsunterlagen')
|
|
matchedTemplateIds.add('krankmeldungen')
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Buchhaltung (data-buchhaltung = true)
|
|
// -------------------------------------------------------------------------
|
|
if (getBool('data-buchhaltung')) {
|
|
matchedTemplateIds.add('buchhaltungsbelege')
|
|
matchedTemplateIds.add('rechnungen')
|
|
matchedTemplateIds.add('steuererklaerungen')
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Vertraege (data-vertraege = true)
|
|
// -------------------------------------------------------------------------
|
|
if (getBool('data-vertraege')) {
|
|
matchedTemplateIds.add('vertraege')
|
|
matchedTemplateIds.add('geschaeftsbriefe')
|
|
matchedTemplateIds.add('kundenstammdaten')
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Marketing (data-marketing = true)
|
|
// -------------------------------------------------------------------------
|
|
if (getBool('data-marketing')) {
|
|
matchedTemplateIds.add('newsletter-einwilligungen')
|
|
matchedTemplateIds.add('crm-kontakthistorie')
|
|
matchedTemplateIds.add('cookie-consent-logs')
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Video (data-video = true)
|
|
// -------------------------------------------------------------------------
|
|
if (getBool('data-video')) {
|
|
matchedTemplateIds.add('videoueberwachung')
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Website (org-website = true)
|
|
// -------------------------------------------------------------------------
|
|
if (getBool('org-website')) {
|
|
matchedTemplateIds.add('webserver-logs')
|
|
matchedTemplateIds.add('cookie-consent-logs')
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// ERP/CRM (sys-erp = true)
|
|
// -------------------------------------------------------------------------
|
|
if (getBool('sys-erp')) {
|
|
matchedTemplateIds.add('kundenstammdaten')
|
|
matchedTemplateIds.add('crm-kontakthistorie')
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Backup (sys-backup = true)
|
|
// -------------------------------------------------------------------------
|
|
if (getBool('sys-backup')) {
|
|
matchedTemplateIds.add('backup-daten')
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Gesundheitsdaten (special-gesundheit = true)
|
|
// -------------------------------------------------------------------------
|
|
if (getBool('special-gesundheit')) {
|
|
// Ensure krankmeldungen is included even without full HR data
|
|
matchedTemplateIds.add('krankmeldungen')
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Resolve matched templates from catalog
|
|
// -------------------------------------------------------------------------
|
|
const matchedTemplates: BaselineTemplate[] = []
|
|
for (const templateId of matchedTemplateIds) {
|
|
const template = BASELINE_TEMPLATES.find(t => t.templateId === templateId)
|
|
if (template) {
|
|
matchedTemplates.push(template)
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Convert to policies
|
|
// -------------------------------------------------------------------------
|
|
const generatedPolicies: LoeschfristPolicy[] = matchedTemplates.map(template =>
|
|
templateToPolicy(template)
|
|
)
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Additional storage locations
|
|
// -------------------------------------------------------------------------
|
|
const additionalStorageLocations: StorageLocation[] = []
|
|
|
|
if (getBool('sys-cloud')) {
|
|
const cloudLocation: StorageLocation = {
|
|
id: crypto.randomUUID(),
|
|
name: 'Cloud-Speicher',
|
|
type: 'CLOUD',
|
|
isBackup: false,
|
|
provider: null,
|
|
deletionCapable: true,
|
|
}
|
|
additionalStorageLocations.push(cloudLocation)
|
|
|
|
// Add Cloud storage location to all generated policies
|
|
for (const policy of generatedPolicies) {
|
|
policy.storageLocations.push({ ...cloudLocation, id: crypto.randomUUID() })
|
|
}
|
|
}
|
|
|
|
if (getBool('sys-backup')) {
|
|
const backupLocation: StorageLocation = {
|
|
id: crypto.randomUUID(),
|
|
name: 'Backup-System',
|
|
type: 'BACKUP',
|
|
isBackup: true,
|
|
provider: null,
|
|
deletionCapable: true,
|
|
}
|
|
additionalStorageLocations.push(backupLocation)
|
|
|
|
// Add Backup storage location to all generated policies
|
|
for (const policy of generatedPolicies) {
|
|
policy.storageLocations.push({ ...backupLocation, id: crypto.randomUUID() })
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Legal Hold
|
|
// -------------------------------------------------------------------------
|
|
const hasLegalHoldRequirement = getBool('special-legal-hold')
|
|
|
|
// If legal hold is active, mark all generated policies accordingly
|
|
if (hasLegalHoldRequirement) {
|
|
for (const policy of generatedPolicies) {
|
|
policy.hasActiveLegalHold = true
|
|
policy.deletionTrigger = 'LEGAL_HOLD'
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Tag policies with profiling metadata
|
|
// -------------------------------------------------------------------------
|
|
const branche = getString('org-branche')
|
|
const mitarbeiter = getString('org-mitarbeiter')
|
|
|
|
for (const policy of generatedPolicies) {
|
|
policy.tags = [
|
|
...policy.tags,
|
|
'profiling-generated',
|
|
...(branche ? [`branche:${branche}`] : []),
|
|
...(mitarbeiter ? [`groesse:${mitarbeiter}`] : []),
|
|
]
|
|
}
|
|
|
|
return {
|
|
matchedTemplates,
|
|
generatedPolicies,
|
|
additionalStorageLocations,
|
|
hasLegalHoldRequirement,
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// COMPLIANCE SCOPE INTEGRATION
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Prefill Loeschfristen profiling answers from Compliance Scope Engine answers.
|
|
* The Scope Engine acts as the "Single Source of Truth" for organizational questions.
|
|
*/
|
|
export function prefillFromScopeAnswers(
|
|
scopeAnswers: import('./compliance-scope-types').ScopeProfilingAnswer[]
|
|
): ProfilingAnswer[] {
|
|
const { exportToLoeschfristenAnswers } = require('./compliance-scope-profiling')
|
|
const exported = exportToLoeschfristenAnswers(scopeAnswers) as Array<{ questionId: string; value: unknown }>
|
|
return exported.map(item => ({
|
|
questionId: item.questionId,
|
|
value: item.value as string | string[] | boolean | number,
|
|
}))
|
|
}
|
|
|
|
/**
|
|
* Get the list of Loeschfristen question IDs that are prefilled from Scope answers.
|
|
* These questions should show "Aus Scope-Analyse uebernommen" hint.
|
|
*/
|
|
export const SCOPE_PREFILLED_LF_QUESTIONS = [
|
|
'org-branche',
|
|
'org-mitarbeiter',
|
|
'org-geschaeftsmodell',
|
|
'org-website',
|
|
'data-hr',
|
|
'data-buchhaltung',
|
|
'data-vertraege',
|
|
'data-marketing',
|
|
'data-video',
|
|
'sys-cloud',
|
|
'sys-erp',
|
|
]
|