- tom-generator/export/zip.ts: extract private helpers to zip-helpers.ts (544→342 LOC) - tom-generator/export/docx.ts: extract private helpers to docx-helpers.ts (525→378 LOC) - tom-generator/export/pdf.ts: extract private helpers to pdf-helpers.ts (517→446 LOC) - tom-generator/demo-data/index.ts: extract DEMO_RISK_PROFILES + DEMO_EVIDENCE_DOCUMENTS to demo-data-part2.ts (518→360 LOC) - einwilligungen/generator/privacy-policy-sections.ts: extract sections 5-7 to part2 (559→313 LOC) - einwilligungen/export/pdf.ts: extract HTML/CSS helpers to pdf-helpers.ts (505→296 LOC) - vendor-compliance/context.tsx: extract API action hooks to context-actions.tsx (509→286 LOC) All originals re-export from sibling files — zero consumer import changes needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
314 lines
11 KiB
TypeScript
314 lines
11 KiB
TypeScript
/**
|
|
* Privacy Policy Section Generators (Sections 1-4)
|
|
*
|
|
* Generiert die ersten 4 Abschnitte der Datenschutzerklaerung (DSI).
|
|
* Sections 5-7 live in privacy-policy-sections-part2.ts.
|
|
*/
|
|
|
|
import {
|
|
DataPoint,
|
|
DataPointCategory,
|
|
CompanyInfo,
|
|
PrivacyPolicySection,
|
|
SupportedLanguage,
|
|
LocalizedText,
|
|
RetentionMatrixEntry,
|
|
LegalBasis,
|
|
CATEGORY_METADATA,
|
|
LEGAL_BASIS_INFO,
|
|
RETENTION_PERIOD_INFO,
|
|
} from '../types'
|
|
|
|
// Re-export sections 5-7 for backward compatibility
|
|
export {
|
|
generateRecipientsSection,
|
|
generateRetentionSection,
|
|
generateSpecialCategoriesSection,
|
|
generateRightsSection,
|
|
} from './privacy-policy-sections-part2'
|
|
|
|
// =============================================================================
|
|
// KONSTANTEN
|
|
// =============================================================================
|
|
|
|
const ALL_CATEGORIES: DataPointCategory[] = [
|
|
'MASTER_DATA', 'CONTACT_DATA', 'AUTHENTICATION', 'CONSENT',
|
|
'COMMUNICATION', 'PAYMENT', 'USAGE_DATA', 'LOCATION',
|
|
'DEVICE_DATA', 'MARKETING', 'ANALYTICS', 'SOCIAL_MEDIA',
|
|
'HEALTH_DATA', 'EMPLOYEE_DATA', 'CONTRACT_DATA', 'LOG_DATA',
|
|
'AI_DATA', 'SECURITY',
|
|
]
|
|
|
|
const ALL_LEGAL_BASES: LegalBasis[] = [
|
|
'CONTRACT', 'CONSENT', 'EXPLICIT_CONSENT', 'LEGITIMATE_INTEREST',
|
|
'LEGAL_OBLIGATION', 'VITAL_INTERESTS', 'PUBLIC_INTEREST',
|
|
]
|
|
|
|
// =============================================================================
|
|
// HELPER FUNCTIONS
|
|
// =============================================================================
|
|
|
|
function t(text: LocalizedText, language: SupportedLanguage): string {
|
|
return text[language]
|
|
}
|
|
|
|
function groupByCategory(dataPoints: DataPoint[]): Map<DataPointCategory, DataPoint[]> {
|
|
const grouped = new Map<DataPointCategory, DataPoint[]>()
|
|
for (const dp of dataPoints) {
|
|
const existing = grouped.get(dp.category) || []
|
|
grouped.set(dp.category, [...existing, dp])
|
|
}
|
|
return grouped
|
|
}
|
|
|
|
function groupByLegalBasis(dataPoints: DataPoint[]): Map<LegalBasis, DataPoint[]> {
|
|
const grouped = new Map<LegalBasis, DataPoint[]>()
|
|
for (const dp of dataPoints) {
|
|
const existing = grouped.get(dp.legalBasis) || []
|
|
grouped.set(dp.legalBasis, [...existing, dp])
|
|
}
|
|
return grouped
|
|
}
|
|
|
|
function extractThirdParties(dataPoints: DataPoint[]): string[] {
|
|
const thirdParties = new Set<string>()
|
|
for (const dp of dataPoints) {
|
|
for (const recipient of dp.thirdPartyRecipients) {
|
|
thirdParties.add(recipient)
|
|
}
|
|
}
|
|
return Array.from(thirdParties).sort()
|
|
}
|
|
|
|
export function formatDate(date: Date, language: SupportedLanguage): string {
|
|
return date.toLocaleDateString(language === 'de' ? 'de-DE' : 'en-US', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
})
|
|
}
|
|
|
|
// =============================================================================
|
|
// SECTION GENERATORS
|
|
// =============================================================================
|
|
|
|
export function generateControllerSection(
|
|
companyInfo: CompanyInfo,
|
|
language: SupportedLanguage
|
|
): PrivacyPolicySection {
|
|
const title: LocalizedText = {
|
|
de: '1. Verantwortlicher',
|
|
en: '1. Data Controller',
|
|
}
|
|
|
|
const dpoSection = companyInfo.dpoName
|
|
? language === 'de'
|
|
? `\n\n**Datenschutzbeauftragter:**\n${companyInfo.dpoName}${companyInfo.dpoEmail ? `\nE-Mail: ${companyInfo.dpoEmail}` : ''}${companyInfo.dpoPhone ? `\nTelefon: ${companyInfo.dpoPhone}` : ''}`
|
|
: `\n\n**Data Protection Officer:**\n${companyInfo.dpoName}${companyInfo.dpoEmail ? `\nEmail: ${companyInfo.dpoEmail}` : ''}${companyInfo.dpoPhone ? `\nPhone: ${companyInfo.dpoPhone}` : ''}`
|
|
: ''
|
|
|
|
const content: LocalizedText = {
|
|
de: `Verantwortlich fuer die Datenverarbeitung auf dieser Website ist:
|
|
|
|
**${companyInfo.name}**
|
|
${companyInfo.address}
|
|
${companyInfo.postalCode} ${companyInfo.city}
|
|
${companyInfo.country}
|
|
|
|
E-Mail: ${companyInfo.email}${companyInfo.phone ? `\nTelefon: ${companyInfo.phone}` : ''}${companyInfo.website ? `\nWebsite: ${companyInfo.website}` : ''}${companyInfo.registrationNumber ? `\n\nHandelsregister: ${companyInfo.registrationNumber}` : ''}${companyInfo.vatId ? `\nUSt-IdNr.: ${companyInfo.vatId}` : ''}${dpoSection}`,
|
|
en: `The controller responsible for data processing on this website is:
|
|
|
|
**${companyInfo.name}**
|
|
${companyInfo.address}
|
|
${companyInfo.postalCode} ${companyInfo.city}
|
|
${companyInfo.country}
|
|
|
|
Email: ${companyInfo.email}${companyInfo.phone ? `\nPhone: ${companyInfo.phone}` : ''}${companyInfo.website ? `\nWebsite: ${companyInfo.website}` : ''}${companyInfo.registrationNumber ? `\n\nCommercial Register: ${companyInfo.registrationNumber}` : ''}${companyInfo.vatId ? `\nVAT ID: ${companyInfo.vatId}` : ''}${dpoSection}`,
|
|
}
|
|
|
|
return {
|
|
id: 'controller',
|
|
order: 1,
|
|
title,
|
|
content,
|
|
dataPointIds: [],
|
|
isRequired: true,
|
|
isGenerated: false,
|
|
}
|
|
}
|
|
|
|
export function generateDataCollectionSection(
|
|
dataPoints: DataPoint[],
|
|
language: SupportedLanguage
|
|
): PrivacyPolicySection {
|
|
const title: LocalizedText = {
|
|
de: '2. Erhobene personenbezogene Daten',
|
|
en: '2. Personal Data We Collect',
|
|
}
|
|
|
|
const grouped = groupByCategory(dataPoints)
|
|
const sections: string[] = []
|
|
const hasSpecialCategoryData = dataPoints.some(dp => dp.isSpecialCategory || dp.category === 'HEALTH_DATA')
|
|
|
|
for (const category of ALL_CATEGORIES) {
|
|
const categoryData = grouped.get(category)
|
|
if (!categoryData || categoryData.length === 0) continue
|
|
|
|
const categoryMeta = CATEGORY_METADATA[category]
|
|
if (!categoryMeta) continue
|
|
|
|
const categoryTitle = t(categoryMeta.name, language)
|
|
|
|
let categoryNote = ''
|
|
if (category === 'HEALTH_DATA') {
|
|
categoryNote = language === 'de'
|
|
? `\n\n> **Hinweis:** Diese Daten gehoeren zu den besonderen Kategorien personenbezogener Daten gemaess Art. 9 DSGVO und erfordern eine ausdrueckliche Einwilligung.`
|
|
: `\n\n> **Note:** This data belongs to special categories of personal data under Art. 9 GDPR and requires explicit consent.`
|
|
} else if (category === 'EMPLOYEE_DATA') {
|
|
categoryNote = language === 'de'
|
|
? `\n\n> **Hinweis:** Die Verarbeitung von Beschaeftigtendaten erfolgt gemaess § 26 BDSG.`
|
|
: `\n\n> **Note:** Processing of employee data is carried out in accordance with § 26 BDSG (German Federal Data Protection Act).`
|
|
} else if (category === 'AI_DATA') {
|
|
categoryNote = language === 'de'
|
|
? `\n\n> **Hinweis:** Die Verarbeitung von KI-bezogenen Daten unterliegt den Transparenzpflichten des AI Acts.`
|
|
: `\n\n> **Note:** Processing of AI-related data is subject to AI Act transparency requirements.`
|
|
}
|
|
|
|
const dataList = categoryData
|
|
.map((dp) => {
|
|
const specialTag = dp.isSpecialCategory
|
|
? (language === 'de' ? ' *(Art. 9 DSGVO)*' : ' *(Art. 9 GDPR)*')
|
|
: ''
|
|
return `- **${t(dp.name, language)}**${specialTag}: ${t(dp.description, language)}`
|
|
})
|
|
.join('\n')
|
|
|
|
sections.push(`### ${categoryMeta.code}. ${categoryTitle}\n\n${dataList}${categoryNote}`)
|
|
}
|
|
|
|
const intro: LocalizedText = {
|
|
de: 'Wir erheben und verarbeiten die folgenden personenbezogenen Daten:',
|
|
en: 'We collect and process the following personal data:',
|
|
}
|
|
|
|
const specialCategoryNote: LocalizedText = hasSpecialCategoryData
|
|
? {
|
|
de: '\n\n**Wichtig:** Einige der unten aufgefuehrten Daten gehoeren zu den besonderen Kategorien personenbezogener Daten nach Art. 9 DSGVO und werden nur mit Ihrer ausdruecklichen Einwilligung verarbeitet.',
|
|
en: '\n\n**Important:** Some of the data listed below belongs to special categories of personal data under Art. 9 GDPR and is only processed with your explicit consent.',
|
|
}
|
|
: { de: '', en: '' }
|
|
|
|
const content: LocalizedText = {
|
|
de: `${intro.de}${specialCategoryNote.de}\n\n${sections.join('\n\n')}`,
|
|
en: `${intro.en}${specialCategoryNote.en}\n\n${sections.join('\n\n')}`,
|
|
}
|
|
|
|
return {
|
|
id: 'data-collection',
|
|
order: 2,
|
|
title,
|
|
content,
|
|
dataPointIds: dataPoints.map((dp) => dp.id),
|
|
isRequired: true,
|
|
isGenerated: true,
|
|
}
|
|
}
|
|
|
|
export function generatePurposesSection(
|
|
dataPoints: DataPoint[],
|
|
language: SupportedLanguage
|
|
): PrivacyPolicySection {
|
|
const title: LocalizedText = {
|
|
de: '3. Zwecke der Datenverarbeitung',
|
|
en: '3. Purposes of Data Processing',
|
|
}
|
|
|
|
const purposes = new Map<string, DataPoint[]>()
|
|
for (const dp of dataPoints) {
|
|
const purpose = t(dp.purpose, language)
|
|
const existing = purposes.get(purpose) || []
|
|
purposes.set(purpose, [...existing, dp])
|
|
}
|
|
|
|
const purposeList = Array.from(purposes.entries())
|
|
.map(([purpose, dps]) => {
|
|
const dataNames = dps.map((dp) => t(dp.name, language)).join(', ')
|
|
return `- **${purpose}**\n Betroffene Daten: ${dataNames}`
|
|
})
|
|
.join('\n\n')
|
|
|
|
const content: LocalizedText = {
|
|
de: `Wir verarbeiten Ihre personenbezogenen Daten fuer folgende Zwecke:\n\n${purposeList}`,
|
|
en: `We process your personal data for the following purposes:\n\n${purposeList}`,
|
|
}
|
|
|
|
return {
|
|
id: 'purposes',
|
|
order: 3,
|
|
title,
|
|
content,
|
|
dataPointIds: dataPoints.map((dp) => dp.id),
|
|
isRequired: true,
|
|
isGenerated: true,
|
|
}
|
|
}
|
|
|
|
export function generateLegalBasisSection(
|
|
dataPoints: DataPoint[],
|
|
language: SupportedLanguage
|
|
): PrivacyPolicySection {
|
|
const title: LocalizedText = {
|
|
de: '4. Rechtsgrundlagen der Verarbeitung',
|
|
en: '4. Legal Basis for Processing',
|
|
}
|
|
|
|
const grouped = groupByLegalBasis(dataPoints)
|
|
const sections: string[] = []
|
|
|
|
for (const basis of ALL_LEGAL_BASES) {
|
|
const basisData = grouped.get(basis)
|
|
if (!basisData || basisData.length === 0) continue
|
|
|
|
const basisInfo = LEGAL_BASIS_INFO[basis]
|
|
if (!basisInfo) continue
|
|
|
|
const basisTitle = `${t(basisInfo.name, language)} (${basisInfo.article})`
|
|
const basisDesc = t(basisInfo.description, language)
|
|
|
|
let additionalWarning = ''
|
|
if (basis === 'EXPLICIT_CONSENT') {
|
|
additionalWarning = language === 'de'
|
|
? `\n\n> **Wichtig:** Fuer die Verarbeitung dieser besonderen Kategorien personenbezogener Daten (Art. 9 DSGVO) ist eine separate, ausdrueckliche Einwilligung erforderlich, die Sie jederzeit widerrufen koennen.`
|
|
: `\n\n> **Important:** Processing of these special categories of personal data (Art. 9 GDPR) requires a separate, explicit consent that you can withdraw at any time.`
|
|
}
|
|
|
|
const dataLabel = language === 'de' ? 'Betroffene Daten' : 'Affected Data'
|
|
const dataList = basisData
|
|
.map((dp) => {
|
|
const specialTag = dp.isSpecialCategory
|
|
? (language === 'de' ? ' *(Art. 9 DSGVO)*' : ' *(Art. 9 GDPR)*')
|
|
: ''
|
|
return `- ${t(dp.name, language)}${specialTag}: ${t(dp.legalBasisJustification, language)}`
|
|
})
|
|
.join('\n')
|
|
|
|
sections.push(`### ${basisTitle}\n\n${basisDesc}${additionalWarning}\n\n**${dataLabel}:**\n${dataList}`)
|
|
}
|
|
|
|
const content: LocalizedText = {
|
|
de: `Die Verarbeitung Ihrer personenbezogenen Daten erfolgt auf Grundlage folgender Rechtsgrundlagen:\n\n${sections.join('\n\n')}`,
|
|
en: `The processing of your personal data is based on the following legal grounds:\n\n${sections.join('\n\n')}`,
|
|
}
|
|
|
|
return {
|
|
id: 'legal-basis',
|
|
order: 4,
|
|
title,
|
|
content,
|
|
dataPointIds: dataPoints.map((dp) => dp.id),
|
|
isRequired: true,
|
|
isGenerated: true,
|
|
}
|
|
}
|
|
|