- 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>
300 lines
11 KiB
TypeScript
300 lines
11 KiB
TypeScript
/**
|
|
* Privacy Policy Section Generators (Part 2)
|
|
*
|
|
* Sections 5-7: recipients, retention, special-categories, rights
|
|
* Extracted from privacy-policy-sections.ts to stay under 500 LOC hard cap.
|
|
*/
|
|
|
|
import {
|
|
DataPoint,
|
|
DataPointCategory,
|
|
CompanyInfo,
|
|
PrivacyPolicySection,
|
|
SupportedLanguage,
|
|
LocalizedText,
|
|
RetentionMatrixEntry,
|
|
LegalBasis,
|
|
CATEGORY_METADATA,
|
|
LEGAL_BASIS_INFO,
|
|
RETENTION_PERIOD_INFO,
|
|
} from '../types'
|
|
|
|
// Private helpers (duplicated locally to avoid cross-file private import)
|
|
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 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 generateRecipientsSection(
|
|
dataPoints: DataPoint[],
|
|
language: SupportedLanguage
|
|
): PrivacyPolicySection {
|
|
const title: LocalizedText = {
|
|
de: '5. Empfaenger und Datenweitergabe',
|
|
en: '5. Recipients and Data Sharing',
|
|
}
|
|
|
|
const thirdParties = extractThirdParties(dataPoints)
|
|
|
|
if (thirdParties.length === 0) {
|
|
const content: LocalizedText = {
|
|
de: 'Wir geben Ihre personenbezogenen Daten grundsaetzlich nicht an Dritte weiter, es sei denn, dies ist zur Vertragserfuellung erforderlich oder Sie haben ausdruecklich eingewilligt.',
|
|
en: 'We generally do not share your personal data with third parties unless this is necessary for contract performance or you have expressly consented.',
|
|
}
|
|
return {
|
|
id: 'recipients',
|
|
order: 5,
|
|
title,
|
|
content,
|
|
dataPointIds: [],
|
|
isRequired: true,
|
|
isGenerated: false,
|
|
}
|
|
}
|
|
|
|
const recipientDetails = new Map<string, DataPoint[]>()
|
|
for (const dp of dataPoints) {
|
|
for (const recipient of dp.thirdPartyRecipients) {
|
|
const existing = recipientDetails.get(recipient) || []
|
|
recipientDetails.set(recipient, [...existing, dp])
|
|
}
|
|
}
|
|
|
|
const recipientList = Array.from(recipientDetails.entries())
|
|
.map(([recipient, dps]) => {
|
|
const dataNames = dps.map((dp) => t(dp.name, language)).join(', ')
|
|
return `- **${recipient}**: ${dataNames}`
|
|
})
|
|
.join('\n')
|
|
|
|
const content: LocalizedText = {
|
|
de: `Wir uebermitteln Ihre personenbezogenen Daten an folgende Empfaenger bzw. Kategorien von Empfaengern:\n\n${recipientList}\n\nMit allen Auftragsverarbeitern haben wir Auftragsverarbeitungsvertraege nach Art. 28 DSGVO abgeschlossen.`,
|
|
en: `We share your personal data with the following recipients or categories of recipients:\n\n${recipientList}\n\nWe have concluded data processing agreements pursuant to Art. 28 GDPR with all processors.`,
|
|
}
|
|
|
|
return {
|
|
id: 'recipients',
|
|
order: 5,
|
|
title,
|
|
content,
|
|
dataPointIds: dataPoints.filter((dp) => dp.thirdPartyRecipients.length > 0).map((dp) => dp.id),
|
|
isRequired: true,
|
|
isGenerated: true,
|
|
}
|
|
}
|
|
|
|
export function generateRetentionSection(
|
|
dataPoints: DataPoint[],
|
|
retentionMatrix: RetentionMatrixEntry[],
|
|
language: SupportedLanguage
|
|
): PrivacyPolicySection {
|
|
const title: LocalizedText = {
|
|
de: '6. Speicherdauer',
|
|
en: '6. Data Retention',
|
|
}
|
|
|
|
const grouped = groupByCategory(dataPoints)
|
|
const sections: string[] = []
|
|
|
|
for (const entry of retentionMatrix) {
|
|
const categoryData = grouped.get(entry.category)
|
|
if (!categoryData || categoryData.length === 0) continue
|
|
|
|
const categoryName = t(entry.categoryName, language)
|
|
const standardPeriod = t(RETENTION_PERIOD_INFO[entry.standardPeriod].label, language)
|
|
|
|
const dataRetention = categoryData
|
|
.map((dp) => {
|
|
const period = t(RETENTION_PERIOD_INFO[dp.retentionPeriod].label, language)
|
|
return `- ${t(dp.name, language)}: ${period}`
|
|
})
|
|
.join('\n')
|
|
|
|
sections.push(`### ${categoryName}\n\n**Standardfrist:** ${standardPeriod}\n\n${dataRetention}`)
|
|
}
|
|
|
|
const content: LocalizedText = {
|
|
de: `Wir speichern Ihre personenbezogenen Daten nur so lange, wie dies fuer die jeweiligen Zwecke erforderlich ist oder gesetzliche Aufbewahrungsfristen bestehen.\n\n${sections.join('\n\n')}`,
|
|
en: `We store your personal data only for as long as is necessary for the respective purposes or as required by statutory retention periods.\n\n${sections.join('\n\n')}`,
|
|
}
|
|
|
|
return {
|
|
id: 'retention',
|
|
order: 6,
|
|
title,
|
|
content,
|
|
dataPointIds: dataPoints.map((dp) => dp.id),
|
|
isRequired: true,
|
|
isGenerated: true,
|
|
}
|
|
}
|
|
|
|
export function generateSpecialCategoriesSection(
|
|
dataPoints: DataPoint[],
|
|
language: SupportedLanguage
|
|
): PrivacyPolicySection | null {
|
|
const specialCategoryDataPoints = dataPoints.filter(dp => dp.isSpecialCategory || dp.category === 'HEALTH_DATA')
|
|
|
|
if (specialCategoryDataPoints.length === 0) {
|
|
return null
|
|
}
|
|
|
|
const title: LocalizedText = {
|
|
de: '6a. Besondere Kategorien personenbezogener Daten (Art. 9 DSGVO)',
|
|
en: '6a. Special Categories of Personal Data (Art. 9 GDPR)',
|
|
}
|
|
|
|
const dataList = specialCategoryDataPoints
|
|
.map((dp) => `- **${t(dp.name, language)}**: ${t(dp.description, language)}`)
|
|
.join('\n')
|
|
|
|
const content: LocalizedText = {
|
|
de: `Gemaess Art. 9 DSGVO verarbeiten wir auch besondere Kategorien personenbezogener Daten, die einen erhoehten Schutz geniessen. Diese Daten umfassen:
|
|
|
|
${dataList}
|
|
|
|
### Ihre ausdrueckliche Einwilligung
|
|
|
|
Die Verarbeitung dieser besonderen Kategorien personenbezogener Daten erfolgt nur auf Grundlage Ihrer **ausdruecklichen Einwilligung** gemaess Art. 9 Abs. 2 lit. a DSGVO.
|
|
|
|
### Ihre Rechte bei Art. 9 Daten
|
|
|
|
- Sie koennen Ihre Einwilligung **jederzeit widerrufen**
|
|
- Der Widerruf beruehrt nicht die Rechtmaessigkeit der bisherigen Verarbeitung
|
|
- Bei Widerruf werden Ihre Daten unverzueglich geloescht
|
|
- Sie haben das Recht auf **Auskunft, Berichtigung und Loeschung**
|
|
|
|
### Besondere Schutzmassnahmen
|
|
|
|
Fuer diese sensiblen Daten haben wir besondere technische und organisatorische Massnahmen implementiert:
|
|
- Ende-zu-Ende-Verschluesselung
|
|
- Strenge Zugriffskontrolle (Need-to-Know-Prinzip)
|
|
- Audit-Logging aller Zugriffe
|
|
- Regelmaessige Datenschutz-Folgenabschaetzungen`,
|
|
en: `In accordance with Art. 9 GDPR, we also process special categories of personal data that enjoy enhanced protection. This data includes:
|
|
|
|
${dataList}
|
|
|
|
### Your Explicit Consent
|
|
|
|
Processing of these special categories of personal data only takes place on the basis of your **explicit consent** pursuant to Art. 9(2)(a) GDPR.
|
|
|
|
### Your Rights Regarding Art. 9 Data
|
|
|
|
- You can **withdraw your consent at any time**
|
|
- Withdrawal does not affect the lawfulness of previous processing
|
|
- Upon withdrawal, your data will be deleted immediately
|
|
- You have the right to **access, rectification, and erasure**
|
|
|
|
### Special Protection Measures
|
|
|
|
For this sensitive data, we have implemented special technical and organizational measures:
|
|
- End-to-end encryption
|
|
- Strict access control (need-to-know principle)
|
|
- Audit logging of all access
|
|
- Regular data protection impact assessments`,
|
|
}
|
|
|
|
return {
|
|
id: 'special-categories',
|
|
order: 6.5,
|
|
title,
|
|
content,
|
|
dataPointIds: specialCategoryDataPoints.map((dp) => dp.id),
|
|
isRequired: false,
|
|
isGenerated: true,
|
|
}
|
|
}
|
|
|
|
export function generateRightsSection(language: SupportedLanguage): PrivacyPolicySection {
|
|
const title: LocalizedText = {
|
|
de: '7. Ihre Rechte als betroffene Person',
|
|
en: '7. Your Rights as a Data Subject',
|
|
}
|
|
|
|
const content: LocalizedText = {
|
|
de: `Sie haben gegenueber uns folgende Rechte hinsichtlich der Sie betreffenden personenbezogenen Daten:
|
|
|
|
### Auskunftsrecht (Art. 15 DSGVO)
|
|
Sie haben das Recht, Auskunft ueber die von uns verarbeiteten personenbezogenen Daten zu verlangen.
|
|
|
|
### Recht auf Berichtigung (Art. 16 DSGVO)
|
|
Sie haben das Recht, die Berichtigung unrichtiger oder die Vervollstaendigung unvollstaendiger Daten zu verlangen.
|
|
|
|
### Recht auf Loeschung (Art. 17 DSGVO)
|
|
Sie haben das Recht, die Loeschung Ihrer personenbezogenen Daten zu verlangen, sofern keine gesetzlichen Aufbewahrungspflichten entgegenstehen.
|
|
|
|
### Recht auf Einschraenkung der Verarbeitung (Art. 18 DSGVO)
|
|
Sie haben das Recht, die Einschraenkung der Verarbeitung Ihrer Daten zu verlangen.
|
|
|
|
### Recht auf Datenuebertragbarkeit (Art. 20 DSGVO)
|
|
Sie haben das Recht, Ihre Daten in einem strukturierten, gaengigen und maschinenlesbaren Format zu erhalten.
|
|
|
|
### Widerspruchsrecht (Art. 21 DSGVO)
|
|
Sie haben das Recht, der Verarbeitung Ihrer Daten jederzeit zu widersprechen, soweit die Verarbeitung auf berechtigtem Interesse beruht.
|
|
|
|
### Recht auf Widerruf der Einwilligung (Art. 7 Abs. 3 DSGVO)
|
|
Sie haben das Recht, Ihre erteilte Einwilligung jederzeit zu widerrufen. Die Rechtmaessigkeit der aufgrund der Einwilligung bis zum Widerruf erfolgten Verarbeitung wird dadurch nicht beruehrt.
|
|
|
|
### Beschwerderecht bei der Aufsichtsbehoerde (Art. 77 DSGVO)
|
|
Sie haben das Recht, sich bei einer Datenschutz-Aufsichtsbehoerde ueber die Verarbeitung Ihrer personenbezogenen Daten zu beschweren.
|
|
|
|
**Zur Ausuebung Ihrer Rechte wenden Sie sich bitte an die oben angegebenen Kontaktdaten.**`,
|
|
en: `You have the following rights regarding your personal data:
|
|
|
|
### Right of Access (Art. 15 GDPR)
|
|
You have the right to request information about the personal data we process about you.
|
|
|
|
### Right to Rectification (Art. 16 GDPR)
|
|
You have the right to request the correction of inaccurate data or the completion of incomplete data.
|
|
|
|
### Right to Erasure (Art. 17 GDPR)
|
|
You have the right to request the deletion of your personal data, unless statutory retention obligations apply.
|
|
|
|
### Right to Restriction of Processing (Art. 18 GDPR)
|
|
You have the right to request the restriction of processing of your data.
|
|
|
|
### Right to Data Portability (Art. 20 GDPR)
|
|
You have the right to receive your data in a structured, commonly used, and machine-readable format.
|
|
|
|
### Right to Object (Art. 21 GDPR)
|
|
You have the right to object to the processing of your data at any time, insofar as the processing is based on legitimate interest.
|
|
|
|
### Right to Withdraw Consent (Art. 7(3) GDPR)
|
|
You have the right to withdraw your consent at any time. The lawfulness of processing based on consent before its withdrawal is not affected.
|
|
|
|
### Right to Lodge a Complaint with a Supervisory Authority (Art. 77 GDPR)
|
|
You have the right to lodge a complaint with a data protection supervisory authority about the processing of your personal data.
|
|
|
|
**To exercise your rights, please contact us using the contact details provided above.**`,
|
|
}
|
|
|
|
return {
|
|
id: 'rights',
|
|
order: 7,
|
|
title,
|
|
content,
|
|
dataPointIds: [],
|
|
isRequired: true,
|
|
isGenerated: false,
|
|
}
|
|
}
|